Compare commits

...

79 Commits

Author SHA1 Message Date
YunaiV 6a06f520fb feat:【IoT 物联网】调整设备模拟发送消息的接口 2025-06-14 14:37:49 +08:00
YunaiV d346a8d2ae feat:【IoT 物联网】完善设备消息的查询 2025-06-14 11:10:54 +08:00
YunaiV 72245b5b0d feat:【IoT 物联网】优化获取认证信息的逻辑 2025-06-14 09:48:05 +08:00
YunaiV 7b114e1986 feat:【IoT 物联网】移除 deviceKey 参数 2025-06-14 09:06:41 +08:00
YunaiV 7e49c90156 review:【IoT 物联网】MqTT 协议 2025-06-13 23:13:29 +08:00
haohao f1368d9e79 reactor:【IoT 物联网】重构 MQTT 认证和事件处理逻辑,符合 EMQX 官方规范 2025-06-13 15:58:07 +08:00
haohao 2737ffa116 reactor:【IoT 物联网】重构 MQTT 客户端连接和订阅主题流程,更新配置属性 2025-06-13 12:30:25 +08:00
haohao 569eef4a74 feat:【IoT 物联网】更新设备消息处理逻辑,重构 MQTT 下行消息处理器,优化主题构建和消息发布流程 2025-06-13 11:51:05 +08:00
YunaiV b06556da2d feat:【IoT 产品】产品的协议,改成 codec 字段 2025-06-13 07:45:51 +08:00
YunaiV 6488d5bcaa review:【IoT 物联网】mqtt 协议的 review 2025-06-12 22:07:39 +08:00
YunaiV d1b2fb41ae review:【IoT 物联网】mqtt 协议的 review 2025-06-12 22:04:18 +08:00
haohao 7b10b59541 feat:【IoT 物联网】优化 MQTT 配置属性,新增 MQTT 消息处理抽象基类 2025-06-12 19:37:55 +08:00
haohao a4e80d45fe fix:【IoT 物联网】修复设备属性上报和配置设置主题,优化 MQTT 消息处理逻辑,更新相关配置文件 2025-06-12 18:59:16 +08:00
haohao c658ac69c0 reactor:【IoT 物联网】合并 MQTT 上行协议与 HTTP 认证协议,优化消息处理逻辑 2025-06-12 18:06:38 +08:00
YunaiV af0ff1ce26 review:【IoT 物联网】mqtt broker 协议 2025-06-12 09:55:17 +08:00
YunaiV 74db72c9c0 Merge remote-tracking branch 'origin/feature/iot' into feature/iot 2025-06-12 09:07:47 +08:00
YunaiV b37814ec9c reactor:【IoT 物联网】调整物模型相关的时序表,通过 productId 和 deviceId 区分 2025-06-12 09:06:10 +08:00
haohao 21472e8dfa feat:【IoT 物联网】新增 MQTT HTTP 认证协议,优化设备认证逻辑,更新配置文件以支持 HTTP 认证服务 2025-06-11 23:29:24 +08:00
YunaiV c3499af524 reactor:【IoT 物联网】消息处理时,异步进行时间相关的记录 2025-06-11 21:31:01 +08:00
YunaiV 33fed79820 reactor:【IoT 物联网】优化设备缓存的加载逻辑 2025-06-11 20:35:09 +08:00
YunaiV 66b42367cb reactor:【IoT 物联网】重新梳理下行消息的逻辑(未测试,用于相互 review 作用) 2025-06-11 09:56:59 +08:00
haohao 4ea6e08f99 feat:【IoT 物联网】新增设备信息查询功能,优化 MQTT 消息处理逻辑 2025-06-10 10:21:24 +08:00
YunaiV 800a85f7bc reactor:【IoT 物联网】清理 components 和 protocol,基本已经融合到 gateway 2025-06-09 23:28:24 +08:00
YunaiV 479e0356ad reactor:【IoT 物联网】实现 alink 的 IotAlinkDeviceMessageCodec,并尝试接入 http 协议 2025-06-09 21:45:19 +08:00
YunaiV 120029bb17 reactor:【IoT 物联网】实现 alink 的 IotAlinkDeviceMessageCodec,并尝试接入 http 协议 2025-06-09 21:44:47 +08:00
haohao f58cf282dd feat:【IoT 物联网】新增 MQTT 协议支持,包含上行和下行消息处理器,完善设备认证和属性、事件、服务处理逻辑,更新配置以启用 EMQX 组件 2025-06-08 22:24:32 +08:00
haohao d7c57f5023 feat:【IoT 物联网】gateway 新增本地开发环境配置 2025-06-08 21:03:02 +08:00
YunaiV 40a9242691 reactor:【IoT 物联网】阅读 thingsboard 后,增加 IotDeviceMessage 优化的 TODO 2025-06-07 18:03:40 +08:00
YunaiV a0a26c3d64 feat:【IoT 物联网】增加网关 HTTP 协议的鉴权,基于 JWT 轻量级(已测试) 2025-06-03 22:27:04 +08:00
YunaiV 643cc4cfd2 feat:【IoT 物联网】增加网关 HTTP 协议的鉴权,基于 JWT 轻量级 2025-06-03 13:22:55 +08:00
YunaiV 1498389d26 feat:【IoT 物联网】增加(重构) IotDevicePropertyMessageSubscriber 记录设备属性 2025-06-02 15:10:26 +08:00
YunaiV 0bb01eaeeb feat:【IoT 物联网】消息下行时,增加 serverId 的接入 2025-06-02 11:13:44 +08:00
YunaiV ac624b7495 feat:【IoT 物联网】重新实现 IotDeviceLogMessageSubscriber 的日志记录 2025-06-01 10:51:55 +08:00
YunaiV c3485a3f3d reactor:【IoT 物联网】将 http component 合并到 gateway 里 2025-06-01 07:48:30 +08:00
YunaiV 81cbc61f3c reactor:【IoT 物联网】移除 plugin 机制 2025-05-31 10:21:17 +08:00
YunaiV cf52a16f6c reactor:【IoT 物联网】移除 script 脚本,简化逻辑复杂度 2025-05-31 10:02:01 +08:00
YunaiV 02c3aa748b reactor:【IoT 物联网】清理心跳逻辑 2025-05-30 22:34:43 +08:00
YunaiV b4035cb036 reactor:【IoT 物联网】数据下行,基于 messagebus 实现订阅消费 2025-05-30 22:14:46 +08:00
YunaiV 1b59aa9ccb reactor:【IoT 物联网】增加 http 网络组件,接入 rocketmq 消息总线 2025-05-30 20:47:01 +08:00
YunaiV 385cea8d90 reactor:【IoT 物联网】合并 messagebus 和 common 包,统一到 core 包 2025-05-29 07:33:34 +08:00
YunaiV 6cf2eb07d7 feat:【IoT 物联网】增加 iot-common 和 iot-gateway 包 2025-05-28 13:22:22 +08:00
YunaiV 70a0df54e4 feat:【IoT 物联网】增加消息总线(messagebus)的 rocketmq 实现 2025-05-28 09:57:50 +08:00
YunaiV 39fb31d400 feat:【IoT 物联网】增加消息总线(messagebus)的 local 实现 2025-05-25 11:19:35 +08:00
haohao af37176d50 feat:【IOT】新增 MQTT 协议支持及相关消息解析器,完善协议转换器功能 2025-05-24 17:30:32 +08:00
haohao fbb664026d feat:【IOT】新增 HTTP 协议支持及相关解析器,完善协议转换器功能 2025-05-23 23:23:49 +08:00
安浩浩 ab4b148df3 feat:【IOT】添加 IoT 脚本模块及相关配置,删除不再使用的插件模块 2025-05-22 22:23:08 +08:00
YunaiV f124584d06 Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot
# Conflicts:
#	yudao-server/src/main/resources/application-local.yaml
2025-05-22 20:53:41 +08:00
YunaiV d83af87f9f 【代码修复】IoT:网络组件相关 2025-04-12 21:05:49 +08:00
安浩浩 44b835bd4a 【功能新增】IoT: 新增网络组件服务器,支持设备上行和下行处理,添加健康检查接口,配置心跳任务,更新相关依赖和配置文件。 2025-04-09 23:08:24 +08:00
安浩浩 2954445d34 【功能新增】IoT: 新增脚本模块,支持 JavaScript 引擎解析,提供示例脚本和安全性验证功能,更新相关依赖和配置。 2025-04-06 21:59:16 +08:00
安浩浩 ae96ff4a25 【功能修改】IoT: 修改网络组件模块,包含 HTTP 和 EMQX 组件,重构相关配置和处理逻辑,更新文档说明。 2025-04-04 19:21:37 +08:00
YunaiV 72d8511d6b 【代码评审】IoT:网络组件的 review 2025-04-04 07:50:24 +08:00
安浩浩 b5ce269fef 【功能新增】IoT: 重构物联网模块,新增组件支持,更新依赖项,优化代码结构。具体更改包括:将插件模块替换为组件模块,添加 HTTP 和 EMQX 组件,更新相关依赖,优化心跳和下行处理逻辑,增加组件配置属性,完善文档说明。 2025-04-02 21:28:56 +08:00
YunaiV 516e3a2387 【代码评审】IoT:场景联动的 review 2025-03-30 09:57:27 +08:00
芋道源码 2585c4753a
!1306 新增场景联动所需接口
Merge pull request !1306 from puhui999/iot
2025-03-30 01:51:36 +00:00
puhui999 80239e7f26 【功能完善】IoT: 场景联动触发条件参数 2025-03-29 21:21:22 +08:00
puhui999 08c0461a3e 【功能新增】IoT: 获取数据桥梁的精简信息列表 2025-03-29 12:53:13 +08:00
puhui999 5a66037725 【功能新增】IoT: 通过产品标识和设备名称列表获取设备列表 2025-03-29 11:48:30 +08:00
puhui999 b3dcd7b133 【功能新增】IoT: 通过 ProductKey 获得产品 2025-03-29 11:38:13 +08:00
YunaiV e5a74193ba 【代码评审】IoT:数据桥梁的 review 2025-03-25 21:01:12 +08:00
芋道源码 37fdd6247d
!1302 【代码优化】IoT: 物模型、场景联动
Merge pull request !1302 from puhui999/iot
2025-03-25 12:43:06 +00:00
YunaiV eeb1dc4a07 【代码评审】IoT:产品脚本的逻辑 2025-03-25 20:42:21 +08:00
安浩浩 0ae893272b 【功能优化】IoT: 重构脚本服务实现,启用脚本执行和测试逻辑,更新日志记录方式 2025-03-24 18:11:33 +08:00
puhui999 6e48154fb8 Merge remote-tracking branch 'refs/remotes/yudao/feature/iot' into iot 2025-03-24 17:48:02 +08:00
puhui999 d155876f09 【代码优化】IoT: 物模型参数校验 2025-03-24 17:37:22 +08:00
puhui999 343353b8f8 【代码优化】IoT: 场景联动 2025-03-24 16:45:48 +08:00
安浩浩 a9dc654b36 【功能优化】IoT: 注释掉脚本服务相关代码,移除不必要的依赖,简化产品脚本测试逻辑 2025-03-24 16:25:11 +08:00
puhui999 e3e3a00fba Merge remote-tracking branch 'yudao/feature/iot' into iot 2025-03-24 16:14:29 +08:00
puhui999 3266bf0f98 【代码优化】IoT: Redis Stream 数据桥梁执行器 2025-03-24 16:13:19 +08:00
puhui999 cd656fad4b 【代码优化】IoT: 数据桥梁的执行器单元测试优化 2025-03-24 15:45:14 +08:00
安浩浩 9b2389356f 【功能新增】IoT: 增加产品脚本语言、状态和类型枚举,更新相关请求和响应对象以支持新的枚举类型 2025-03-23 22:13:21 +08:00
安浩浩 e26903b06b 【功能新增】IoT: 增加产品脚本管理功能,包括增删改查及测试接口 2025-03-23 21:55:08 +08:00
安浩浩 87a43b8354 【功能新增】IoT: 添加脚本引擎模块,支持设备数据解析和命令生成 2025-03-23 21:03:07 +08:00
YunaiV 2f9d760327 【代码评审】IoT:场景联动 review 2025-03-23 09:41:05 +08:00
芋道源码 35dd91d6c9
!1296 【功能新增】IoT: 规则场景
Merge pull request !1296 from puhui999/iot
2025-03-23 01:03:19 +00:00
puhui999 2073ecb2f3 【功能新增】IoT: 物模型 TSL 获取 2025-03-21 16:45:28 +08:00
puhui999 38e8c85276 【代码优化】redis 配置 2025-03-18 17:36:44 +08:00
puhui999 f118d66006 【代码优化】IoT: 规则场景 config 抽离 2025-03-18 16:51:08 +08:00
puhui999 6d6547be39 【功能新增】IoT: 规则场景基础 CRUD 2025-03-18 16:36:18 +08:00
272 changed files with 5826 additions and 8207 deletions

View File

@ -24,7 +24,7 @@
<!-- <module>yudao-module-crm</module>-->
<!-- <module>yudao-module-erp</module>-->
<!-- <module>yudao-module-ai</module>-->
<!-- <module>yudao-module-iot</module>-->
<module>yudao-module-iot</module>
</modules>
<name>${project.artifactId}</name>

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
@ -312,4 +313,16 @@ public class LocalDateTimeUtils {
}
}
/**
* 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间1970-01-01T00:00:00Z以来的秒数
*
* @param sourceDateTime 需要转换的本地日期时间不能为空
* @return 1970-01-01T00:00:00Z 起的秒数epoch second
* @throws NullPointerException 如果 {@code sourceDateTime} {@code null}
* @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
*/
public static Long toEpochSecond(LocalDateTime sourceDateTime) {
return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
}
}

View File

@ -3,18 +3,22 @@ package cn.iocoder.yudao.framework.common.util.json;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@ -32,7 +36,11 @@ public class JsonUtils {
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
// 解决 LocalDateTime 的序列化
SimpleModule simpleModule = new JavaTimeModule()
.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
objectMapper.registerModules(simpleModule);
}
/**
@ -99,6 +107,18 @@ public class JsonUtils {
}
}
public static <T> T parseObject(byte[] text, Type type) {
if (ArrayUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 将字符串解析成指定类型的对象
* 使用 {@link #parseObject(String, Class)} @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下

View File

@ -60,4 +60,8 @@ public class ObjectUtils {
return Arrays.asList(array).contains(obj);
}
public static boolean isNotAllEmpty(Object... objs) {
return !ObjectUtil.isAllEmpty(objs);
}
}

View File

@ -179,7 +179,7 @@ public class GlobalExceptionHandler {
if(ex.getCause() instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
}else {
} else {
return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao</artifactId>
<groupId>cn.iocoder.boot</groupId>
@ -10,7 +9,8 @@
<modules>
<module>yudao-module-iot-api</module>
<module>yudao-module-iot-biz</module>
<module>yudao-module-iot-plugins</module>
<module>yudao-module-iot-core</module>
<module>yudao-module-iot-gateway</module>
</modules>
<modelVersion>4.0.0</modelVersion>
@ -20,7 +20,7 @@
<name>${project.artifactId}</name>
<description>
物联网模块
<!-- TODO 芋艿:需要补充下说明! -->
<!-- TODO 芋艿:需要补充下说明! -->
</description>
</project>

View File

@ -37,10 +37,10 @@
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>org.pf4j</groupId> <!-- PF4J内置插件机制 -->
<artifactId>pf4j-spring</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.pf4j</groupId> &lt;!&ndash; PF4J内置插件机制 &ndash;&gt;-->
<!-- <artifactId>pf4j-spring</artifactId>-->
<!-- </dependency>-->
<!-- 参数校验 -->
<dependency>

View File

@ -1,93 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
import cn.iocoder.yudao.module.iot.enums.ApiConstants;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 设备数据 Upstream 上行 API
*
* 目的设备 -> 插件 -> 服务端
*
* @author haohao
*/
public interface IotDeviceUpstreamApi {
String PREFIX = ApiConstants.PREFIX + "/device/upstream";
// ========== 设备相关 ==========
/**
* 更新设备状态
*
* @param updateReqDTO 更新设备状态 DTO
*/
@PostMapping(PREFIX + "/update-state")
CommonResult<Boolean> updateDeviceState(@Valid @RequestBody IotDeviceStateUpdateReqDTO updateReqDTO);
/**
* 上报设备属性数据
*
* @param reportReqDTO 上报设备属性数据 DTO
*/
@PostMapping(PREFIX + "/report-property")
CommonResult<Boolean> reportDeviceProperty(@Valid @RequestBody IotDevicePropertyReportReqDTO reportReqDTO);
/**
* 上报设备事件数据
*
* @param reportReqDTO 设备事件
*/
@PostMapping(PREFIX + "/report-event")
CommonResult<Boolean> reportDeviceEvent(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO);
// TODO @芋艿这个需要 plugins 接入下
/**
* 注册设备
*
* @param registerReqDTO 注册设备 DTO
*/
@PostMapping(PREFIX + "/register")
CommonResult<Boolean> registerDevice(@Valid @RequestBody IotDeviceRegisterReqDTO registerReqDTO);
// TODO @芋艿这个需要 plugins 接入下
/**
* 注册子设备
*
* @param registerReqDTO 注册子设备 DTO
*/
@PostMapping(PREFIX + "/register-sub")
CommonResult<Boolean> registerSubDevice(@Valid @RequestBody IotDeviceRegisterSubReqDTO registerReqDTO);
// TODO @芋艿这个需要 plugins 接入下
/**
* 注册设备拓扑
*
* @param addReqDTO 注册设备拓扑 DTO
*/
@PostMapping(PREFIX + "/add-topology")
CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO);
// TODO @芋艿考虑 http 认证
/**
* 认证 Emqx 连接
*
* @param authReqDTO 认证 Emqx 连接 DTO
*/
@PostMapping(PREFIX + "/authenticate-emqx-connection")
CommonResult<Boolean> authenticateEmqxConnection(@Valid @RequestBody IotDeviceEmqxAuthReqDTO authReqDTO);
// ========== 插件相关 ==========
/**
* 心跳插件实例
*
* @param heartbeatReqDTO 心跳插件实例 DTO
*/
@PostMapping(PREFIX + "/heartbeat-plugin-instance")
CommonResult<Boolean> heartbeatPluginInstance(@Valid @RequestBody IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO);
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Map;
/**
* IoT 设备配置设置 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDeviceConfigSetReqDTO extends IotDeviceDownstreamAbstractReqDTO {
/**
* 配置
*/
@NotNull(message = "配置不能为空")
private Map<String, Object> config;
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* IoT 设备下行的抽象 Request DTO
*
* @author 芋道源码
*/
@Data
public abstract class IotDeviceDownstreamAbstractReqDTO {
/**
* 请求编号
*/
private String requestId;
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
}

View File

@ -1,66 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
import cn.hutool.core.map.MapUtil;
import lombok.Data;
import java.util.Map;
/**
* IoT 设备OTA升级下发 Request DTO更新固件消息
*
* @author 芋道源码
*/
@Data
public class IotDeviceOtaUpgradeReqDTO extends IotDeviceDownstreamAbstractReqDTO {
/**
* 固件编号
*/
private Long firmwareId;
/**
* 固件版本
*/
private String version;
/**
* 签名方式
*
* 例如说MD5SHA256
*/
private String signMethod;
/**
* 固件文件签名
*/
private String fileSign;
/**
* 固件文件大小
*/
private Long fileSize;
/**
* 固件文件 URL
*/
private String fileUrl;
/**
* 自定义信息建议使用 JSON 格式
*/
private String information;
public static IotDeviceOtaUpgradeReqDTO build(Map<?, ?> map) {
return new IotDeviceOtaUpgradeReqDTO()
.setFirmwareId(MapUtil.getLong(map, "firmwareId")).setVersion((String) map.get("version"))
.setSignMethod((String) map.get("signMethod")).setFileSign((String) map.get("fileSign"))
.setFileSize(MapUtil.getLong(map, "fileSize")).setFileUrl((String) map.get("fileUrl"))
.setInformation((String) map.get("information"));
}
public static Map<?, ?> build(IotDeviceOtaUpgradeReqDTO dto) {
return MapUtil.builder()
.put("firmwareId", dto.getFirmwareId()).put("version", dto.getVersion())
.put("signMethod", dto.getSignMethod()).put("fileSign", dto.getFileSign())
.put("fileSize", dto.getFileSize()).put("fileUrl", dto.getFileUrl())
.put("information", dto.getInformation())
.build();
}
}

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
// TODO @芋艿 server => plugin => device 是否有必要从阿里云 iot 来看没有这个功能
// TODO @芋艿是不是改成 read 更好在看看阿里云的 topic 设计
/**
* IoT 设备属性获取 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDevicePropertyGetReqDTO extends IotDeviceDownstreamAbstractReqDTO {
/**
* 属性标识数组
*/
@NotEmpty(message = "属性标识数组不能为空")
private List<String> identifiers;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.Map;
/**
* IoT 设备属性设置 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDevicePropertySetReqDTO extends IotDeviceDownstreamAbstractReqDTO {
/**
* 属性参数
*/
@NotEmpty(message = "属性参数不能为空")
private Map<String, Object> properties;
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.Map;
/**
* IoT 设备服务调用 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDeviceServiceInvokeReqDTO extends IotDeviceDownstreamAbstractReqDTO {
/**
* 服务标识
*/
@NotEmpty(message = "服务标识不能为空")
private String identifier;
/**
* 调用参数
*/
private Map<String, Object> params;
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import lombok.Data;
// TODO @芋艿待实现/ota/${productKey}/${deviceName}/progress
/**
* IoT 设备OTA升级进度 Request DTO上报更新固件进度
*
* @author 芋道源码
*/
@Data
public class IotDeviceOtaProgressReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/**
* 固件编号
*/
private Long firmwareId;
/**
* 升级状态
*
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
*/
private Integer status;
/**
* 升级进度百分比
*/
private Integer progress;
/**
* 升级进度描述
*/
private String description;
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
// TODO @芋艿待实现/ota/${productKey}/${deviceName}/pull
/**
* IoT 设备OTA升级下拉 Request DTO拉取固件更新
*
* @author 芋道源码
*/
public class IotDeviceOtaPullReqDTO {
/**
* 固件编号
*/
private Long firmwareId;
/**
* 固件版本
*/
private String version;
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
// TODO @芋艿待实现/ota/${productKey}/${deviceName}/report
/**
* IoT 设备OTA上报 Request DTO上报固件版本
*
* @author 芋道源码
*/
public class IotDeviceOtaReportReqDTO {
/**
* 固件编号
*/
private Long firmwareId;
/**
* 固件版本
*/
private String version;
}

View File

@ -1,12 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import lombok.Data;
/**
* IoT 设备注册自己 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDeviceRegisterReqDTO extends IotDeviceUpstreamAbstractReqDTO {
}

View File

@ -1,43 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* IoT 设备注册子设备 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
// TODO @芋艿看看要不要优化命名
/**
* 子设备数组
*/
@NotEmpty(message = "子设备不能为空")
private List<Device> params;
/**
* 设备信息
*/
@Data
public static class Device {
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
}
}

View File

@ -1,23 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* IoT 设备状态更新 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/**
* 设备状态
*/
@NotNull(message = "设备状态不能为空")
@InEnum(IotDeviceStateEnum.class) // 只使用在线离线
private Integer state;
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
// TODO @芋艿要写清楚是来自设备网关还是设备
/**
* IoT 设备拓扑添加 Request DTO
*/
@Data
public class IotDeviceTopologyAddReqDTO extends IotDeviceUpstreamAbstractReqDTO {
// TODO @芋艿看看要不要优化命名
/**
* 子设备数组
*/
@NotEmpty(message = "子设备不能为空")
private List<IotDeviceRegisterSubReqDTO.Device> params;
/**
* 设备信息
*/
@Data
public static class Device {
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
// TODO @芋艿阿里云还有 sign 签名
}
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* IoT 插件实例心跳 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotPluginInstanceHeartbeatReqDTO {
/**
* 请求编号
*/
@NotEmpty(message = "请求编号不能为空")
private String processId;
/**
* 插件包标识符
*/
@NotEmpty(message = "插件包标识符不能为空")
private String pluginKey;
/**
* 插件实例所在 IP
*/
@NotEmpty(message = "插件实例所在 IP 不能为空")
private String hostIp;
/**
* 插件实例的进程编号
*/
@NotNull(message = "插件实例的进程编号不能为空")
private Integer downstreamPort;
/**
* 是否在线
*/
@NotNull(message = "是否在线不能为空")
private Boolean online;
}

View File

@ -1,4 +0,0 @@
/**
* TODO 芋艿占位
*/
package cn.iocoder.yudao.module.iot.api.device.dto;

View File

@ -1,6 +0,0 @@
/**
* 占位
*
* TODO 芋艿后续删除
*/
package cn.iocoder.yudao.module.iot.api;

View File

@ -1,16 +0,0 @@
package cn.iocoder.yudao.module.iot.enums;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
/**
* API 相关的枚举
*
* @author 芋道源码
*/
public class ApiConstants {
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/iot";
public static final String VERSION = "1.0.0";
}

View File

@ -10,13 +10,9 @@ public class DictTypeConstants {
public static final String PRODUCT_STATUS = "iot_product_status";
public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
public static final String NET_TYPE = "iot_net_type";
public static final String PROTOCOL_TYPE = "iot_protocol_type";
public static final String DATA_FORMAT = "iot_data_format";
public static final String VALIDATE_TYPE = "iot_validate_type";
public static final String CODEC_TYPE = "iot_codec_type";
public static final String DEVICE_STATE = "iot_device_state";
public static final String IOT_DATA_BRIDGE_DIRECTION_ENUM = "iot_data_bridge_direction_enum";
public static final String IOT_DATA_BRIDGE_TYPE_ENUM = "iot_data_bridge_type_enum";
}

View File

@ -30,7 +30,7 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
ErrorCode DEVICE_DOWNSTREAM_FAILED = new ErrorCode(1_050_003_007, "执行失败,原因:{}");
ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
// ========== 产品分类 1-050-004-000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
@ -39,18 +39,6 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
// ========== 插件配置 1-050-006-000 ==========
ErrorCode PLUGIN_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "插件配置不存在");
ErrorCode PLUGIN_INSTALL_FAILED = new ErrorCode(1_050_006_001, "插件安装失败");
ErrorCode PLUGIN_INSTALL_FAILED_FILE_NAME_NOT_MATCH = new ErrorCode(1_050_006_002, "插件安装失败文件名与原插件id不匹配");
ErrorCode PLUGIN_CONFIG_DELETE_FAILED_RUNNING = new ErrorCode(1_050_006_003, "请先停止插件");
ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效");
ErrorCode PLUGIN_CONFIG_KEY_DUPLICATE = new ErrorCode(1_050_006_005, "插件标识已存在");
ErrorCode PLUGIN_START_FAILED = new ErrorCode(1_050_006_006, "插件启动失败");
ErrorCode PLUGIN_STOP_FAILED = new ErrorCode(1_050_006_007, "插件停止失败");
// ========== 插件实例 1-050-007-000 ==========
// ========== 固件相关 1-050-008-000 ==========
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
@ -66,10 +54,10 @@ public interface ErrorCodeConstants {
ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复");
ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试");
// ========== MQTT 通信相关 1-050-009-000 ==========
ErrorCode MQTT_TOPIC_ILLEGAL = new ErrorCode(1_050_009_000, "topic illegal");
// ========== IoT 数据桥梁 1-050-010-000 ==========
ErrorCode DATA_BRIDGE_NOT_EXISTS = new ErrorCode(1_050_010_000, "IoT 数据桥梁不存在");
// ========== IoT 场景联动 1-050-011-000 ==========
ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_011_000, "IoT 场景联动不存在");
}

View File

@ -11,7 +11,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum IotDeviceMessageIdentifierEnum {
PROPERTY_GET("get"), // 下行 TODO 芋艿讨论貌似这个上行更合理device 主动拉取配置 IotDevicePropertyGetReqDTO 一样的配置
PROPERTY_GET("get"), // 下行
PROPERTY_SET("set"), // 下行
PROPERTY_REPORT("report"), // 上行

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.plugin;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 部署方式枚举
*
* @author haohao
*/
@RequiredArgsConstructor
@Getter
public enum IotPluginDeployTypeEnum implements ArrayValuable<Integer> {
JAR(0, "JAR 部署"),
STANDALONE(1, "独立部署");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginDeployTypeEnum::getDeployType).toArray(Integer[]::new);
/**
* 部署方式
*/
private final Integer deployType;
/**
* 部署方式名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.plugin;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 插件状态枚举
*
* @author haohao
*/
@RequiredArgsConstructor
@Getter
public enum IotPluginStatusEnum implements ArrayValuable<Integer> {
STOPPED(0, "停止"),
RUNNING(1, "运行");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.plugin;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 插件类型枚举
*
* @author haohao
*/
@AllArgsConstructor
@Getter
public enum IotPluginTypeEnum implements ArrayValuable<Integer> {
NORMAL(0, "普通插件"),
DEVICE(1, "设备插件");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginTypeEnum::getType).toArray(Integer[]::new);
/**
* 类型
*/
private final Integer type;
/**
* 类型名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 产品数据格式枚举类
*
* @author ahh
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/message-parsing">阿里云 - 什么是消息解析</a>
*/
@AllArgsConstructor
@Getter
public enum IotDataFormatEnum implements ArrayValuable<Integer> {
JSON(0, "标准数据格式JSON"),
CUSTOMIZE(1, "透传/自定义");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataFormatEnum::getType).toArray(Integer[]::new);
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -1,40 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 接入网关协议枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotProtocolTypeEnum implements ArrayValuable<Integer> {
CUSTOM(0, "自定义"),
MODBUS(1, "Modbus"),
OPC_UA(2, "OPC UA"),
ZIGBEE(3, "ZigBee"),
BLE(4, "BLE");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProtocolTypeEnum::getType).toArray(Integer[]::new);
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 数据校验级别枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotValidateTypeEnum implements ArrayValuable<Integer> {
WEAK(0, "弱校验"),
NONE(1, "免校验");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotValidateTypeEnum::getType).toArray(Integer[]::new);
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -50,7 +50,7 @@ public enum IotRuleSceneTriggerConditionParameterOperatorEnum implements ArrayVa
/**
* Spring 表达式 - 目标值数组
*/
public static final String SPRING_EXPRESSION_VALUE_List = "values";
public static final String SPRING_EXPRESSION_VALUE_LIST = "values";
public static IotRuleSceneTriggerConditionParameterOperatorEnum operatorOf(String operator) {
return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values());

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-module-iot</artifactId>
<groupId>cn.iocoder.boot</groupId>
@ -15,7 +14,7 @@
<name>${project.artifactId}</name>
<description>
物联网 模块,主要实现 产品管理、设备管理、协议管理等功能。
<!-- TODO 芋艿:后续补充下 -->
<!-- TODO 芋艿:后续补充下 -->
</description>
<dependencies>
@ -29,6 +28,11 @@
<artifactId>yudao-module-iot-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-iot-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
@ -75,10 +79,11 @@
</dependency>
<!-- 消息队列相关 -->
<!-- TODO @芋艿:临时打开 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<optional>true</optional>
<!-- <optional>true</optional>-->
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
@ -91,47 +96,17 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.pf4j</groupId> <!-- PF4J内置插件机制 -->
<artifactId>pf4j-spring</artifactId>
</dependency>
<!-- TODO @芋艿bom 管理 -->
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>4.0.25</version>
<type>pom</type>
</dependency>
<!-- TODO @芋艿bom 管理 -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>24.1.2</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>24.1.2</version>
</dependency>
<!-- TODO @芋艿:合理注释 -->
<!-- IoT 数据桥梁的执行器所需消息队列。如果您只需要使用 rocketmq 那么则注释掉其它消息队列即可 -->
<!-- IoT 网络组件:接收来自设备的上行数据 -->
<!-- <dependency>-->
<!-- <groupId>org.apache.rocketmq</groupId>-->
<!-- <artifactId>rocketmq-spring-boot-starter</artifactId>-->
<!-- <groupId>cn.iocoder.boot</groupId>-->
<!-- <artifactId>yudao-module-iot-net-component-http</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.kafka</groupId>-->
<!-- <artifactId>spring-kafka</artifactId>-->
<!-- <groupId>cn.iocoder.boot</groupId>-->
<!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-amqp</artifactId>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@ -1,61 +0,0 @@
package cn.iocoder.yudao.module.iot;
import cn.hutool.script.ScriptUtil;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
/**
* TODO 芋艿测试脚本的接入
*/
public class ScriptTest {
public static void main2(String[] args) {
// 创建一个 Groovy 脚本引擎
ScriptEngine engine = ScriptUtil.createGroovyEngine();
// 创建绑定参数
Bindings bindings = engine.createBindings();
bindings.put("name", "Alice");
bindings.put("age", 30);
// 定义一个稍微复杂的 Groovy 脚本
String script = "def greeting = 'Hello, ' + name + '!';\n" +
"def ageInFiveYears = age + 5;\n" +
"def message = greeting + ' In five years, you will be ' + ageInFiveYears + ' years old.';\n" +
"return message.toUpperCase();\n";
try {
// 执行脚本并获取结果
Object result = engine.eval(script, bindings);
System.out.println(result); // 输出: HELLO, ALICE! IN FIVE YEARS, YOU WILL BE 35 YEARS OLD.
} catch (ScriptException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 创建一个 JavaScript 脚本引擎
ScriptEngine jsEngine = ScriptUtil.createJsEngine();
// 创建绑定参数
Bindings jsBindings = jsEngine.createBindings();
jsBindings.put("name", "Bob");
jsBindings.put("age", 25);
// 定义一个简单的 JavaScript 脚本
String jsScript = "var greeting = 'Hello, ' + name + '!';\n" +
"var ageInTenYears = age + 10;\n" +
"var message = greeting + ' In ten years, you will be ' + ageInTenYears + ' years old.';\n" +
"message.toUpperCase();\n";
try {
// 执行脚本并获取结果
Object jsResult = jsEngine.eval(jsScript, jsBindings);
System.out.println(jsResult); // 输出: HELLO, BOB! IN TEN YEARS, YOU WILL BE 35 YEARS OLD.
} catch (ScriptException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.iot.api.device;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* IoT 设备 API 实现类
*
* @author haohao
*/
@RestController
@Validated
@Primary // 保证优先匹配因为 yudao-iot-gateway 也有 IotDeviceCommonApi 的实现并且也可能会被 biz 引入
public class IoTDeviceApiImpl implements IotDeviceCommonApi {
@Resource
private IotDeviceService deviceService;
@Resource
private IotProductService productService;
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
@PermitAll
public CommonResult<Boolean> authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) {
return success(deviceService.authDevice(authReqDTO));
}
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊方便调用暂时使用 POST实际更推荐 GET
@PermitAll
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
IotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
: deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
if (product != null) {
deviceDTO.setCodecType(product.getCodecType());
}
}));
}
}

View File

@ -1,77 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* * 设备数据 Upstream 上行 API 实现类
*/
@RestController
@Validated
public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
@Resource
private IotDeviceUpstreamService deviceUpstreamService;
@Resource
private IotPluginInstanceService pluginInstanceService;
// ========== 设备相关 ==========
@Override
public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
deviceUpstreamService.updateDeviceState(updateReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
deviceUpstreamService.reportDeviceProperty(reportReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO) {
deviceUpstreamService.reportDeviceEvent(reportReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
deviceUpstreamService.registerDevice(registerReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
deviceUpstreamService.registerSubDevice(registerReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
deviceUpstreamService.addDeviceTopology(addReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
return success(result);
}
// ========== 插件相关 ==========
@Override
public CommonResult<Boolean> heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
pluginInstanceService.heartbeatPluginInstance(heartbeatReqDTO);
return success(true);
}
}

View File

@ -1,6 +1,4 @@
/**
* 占位
*
* TODO 芋艿后续删除
* iot API 定义并实现提供给其它模块的 API
*/
package cn.iocoder.yudao.module.iot.api;

View File

@ -6,13 +6,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService;
import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -42,9 +39,7 @@ public class IotDeviceController {
@Resource
private IotDeviceService deviceService;
@Resource
private IotDeviceUpstreamService deviceUpstreamService;
@Resource
private IotDeviceDownstreamService deviceDownstreamService;
private IotDeviceMessageService deviceMessageService;
@PostMapping("/create")
@Operation(summary = "创建设备")
@ -161,28 +156,20 @@ public class IotDeviceController {
ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
}
@PostMapping("/upstream")
@Operation(summary = "设备上行", description = "可用于设备模拟")
@PreAuthorize("@ss.hasPermission('iot:device:upstream')")
public CommonResult<Boolean> upstreamDevice(@Valid @RequestBody IotDeviceUpstreamReqVO upstreamReqVO) {
deviceUpstreamService.upstreamDevice(upstreamReqVO);
return success(true);
@GetMapping("/get-auth-info")
@Operation(summary = "获得设备连接信息")
@PreAuthorize("@ss.hasPermission('iot:device:auth-info')")
public CommonResult<IotDeviceAuthInfoRespVO> getDeviceAuthInfo(@RequestParam("id") Long id) {
return success(deviceService.getDeviceAuthInfo(id));
}
@PostMapping("/downstream")
@Operation(summary = "设备下行", description = "可用于设备模拟")
@PreAuthorize("@ss.hasPermission('iot:device:downstream')")
public CommonResult<Boolean> downstreamDevice(@Valid @RequestBody IotDeviceDownstreamReqVO downstreamReqVO) {
deviceDownstreamService.downstreamDevice(downstreamReqVO);
return success(true);
// TODO @haohao可以使用 @RequestParam("productKey") String productKey, @RequestParam("deviceNames") List<String> deviceNames 来接收哇
@GetMapping("/list-by-product-key-and-names")
@Operation(summary = "通过产品标识和设备名称列表获取设备")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<List<IotDeviceRespVO>> getDevicesByProductKeyAndNames(@Valid IotDeviceByProductKeyAndNamesReqVO reqVO) {
List<IotDeviceDO> devices = deviceService.getDeviceListByProductKeyAndNames(reqVO.getProductKey(), reqVO.getDeviceNames());
return success(BeanUtils.toBean(devices, IotDeviceRespVO.class));
}
// TODO @haohao是不是默认详情接口不返回 secret然后这个接口用于统一返回然后接口名可以更通用一点
@GetMapping("/mqtt-connection-params")
@Operation(summary = "获取 MQTT 连接参数")
@PreAuthorize("@ss.hasPermission('iot:device:mqtt-connection-params')")
public CommonResult<IotDeviceMqttConnectionParamsRespVO> getMqttConnectionParams(@RequestParam("deviceId") Long deviceId) {
return success(deviceService.getMqttConnectionParams(deviceId));
}
}
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.data.IotDeviceLogPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDeviceLogRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 设备日志")
@RestController
@RequestMapping("/iot/device/log")
@Validated
public class IotDeviceLogController {
@Resource
private IotDeviceLogService deviceLogService;
@GetMapping("/page")
@Operation(summary = "获得设备日志分页")
@PreAuthorize("@ss.hasPermission('iot:device:log-query')")
public CommonResult<PageResult<IotDeviceLogRespVO>> getDeviceLogPage(@Valid IotDeviceLogPageReqVO pageReqVO) {
PageResult<IotDeviceLogDO> pageResult = deviceLogService.getDeviceLogPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDeviceLogRespVO.class));
}
}

View File

@ -1,7 +1,23 @@
### 请求 /iot/device/downstream 接口(服务调用) => 成功
### 请求 /iot/device/message/send 接口(属性上报)=> 成功
POST {{baseUrl}}/iot/device/message/send
Content-Type: application/json
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
"deviceId": 25,
"method": "thing.property.post",
"params": {
"width": 1,
"height": "2",
"oneThree": "3"
}
}
### 请求 /iot/device/downstream 接口(服务调用)=> 成功 TODO 芋艿:未更新为最新
POST {{baseUrl}}/iot/device/downstream
Content-Type: application/json
tenant-id: {{adminTenentId}}
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
@ -13,10 +29,10 @@ Authorization: Bearer {{token}}
}
}
### 请求 /iot/device/downstream 接口(属性设置) => 成功
### 请求 /iot/device/downstream 接口(属性设置)=> 成功 TODO 芋艿:未更新为最新
POST {{baseUrl}}/iot/device/downstream
Content-Type: application/json
tenant-id: {{adminTenentId}}
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
@ -28,10 +44,10 @@ Authorization: Bearer {{token}}
}
}
### 请求 /iot/device/downstream 接口(属性获取) => 成功
### 请求 /iot/device/downstream 接口(属性获取)=> 成功 TODO 芋艿:未更新为最新
POST {{baseUrl}}/iot/device/downstream
Content-Type: application/json
tenant-id: {{adminTenentId}}
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
@ -41,10 +57,10 @@ Authorization: Bearer {{token}}
"data": ["xx", "yy"]
}
### 请求 /iot/device/downstream 接口(配置设置) => 成功
### 请求 /iot/device/downstream 接口(配置设置)=> 成功 TODO 芋艿:未更新为最新
POST {{baseUrl}}/iot/device/downstream
Content-Type: application/json
tenant-id: {{adminTenentId}}
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
@ -53,10 +69,10 @@ Authorization: Bearer {{token}}
"identifier": "set"
}
### 请求 /iot/device/downstream 接口OTA 升级) => 成功
### 请求 /iot/device/downstream 接口OTA 升级)=> 成功 TODO 芋艿:未更新为最新
POST {{baseUrl}}/iot/device/downstream
Content-Type: application/json
tenant-id: {{adminTenentId}}
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.message.IotDeviceMessageRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageSendReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 设备消息")
@RestController
@RequestMapping("/iot/device/message")
@Validated
public class IotDeviceMessageController {
@Resource
private IotDeviceMessageService deviceMessageService;
@GetMapping("/page")
@Operation(summary = "获得设备消息分页")
@PreAuthorize("@ss.hasPermission('iot:device:message-query')")
public CommonResult<PageResult<IotDeviceMessageRespVO>> getDeviceLogPage(@Valid IotDeviceMessagePageReqVO pageReqVO) {
PageResult<IotDeviceMessageDO> pageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDeviceMessageRespVO.class));
}
@PostMapping("/send")
@Operation(summary = "发送消息", description = "可用于设备模拟")
@PreAuthorize("@ss.hasPermission('iot:device:message-end')")
public CommonResult<Boolean> upstreamDevice(@Valid @RequestBody IotDeviceMessageSendReqVO sendReqVO) {
deviceMessageService.sendDeviceMessage(BeanUtils.toBean(sendReqVO, IotDeviceMessage.class));
return success(true);
}
}

View File

@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.control;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备下行 Request VO") // 服务调用属性设置属性获取等
@Data
public class IotDeviceDownstreamReqVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
@NotNull(message = "设备编号不能为空")
private Long id;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
@NotEmpty(message = "消息类型不能为空")
@InEnum(IotDeviceMessageTypeEnum.class)
private String type;
@Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
@NotEmpty(message = "标识符不能为空")
private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举类
@Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
private Object data; // 例如说服务调用的 params属性设置的 properties
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.control;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备上行 Request VO") // 属性上报事件上报状态变更等
@Data
public class IotDeviceUpstreamReqVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
@NotNull(message = "设备编号不能为空")
private Long id;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
@NotEmpty(message = "消息类型不能为空")
@InEnum(IotDeviceMessageTypeEnum.class)
private String type;
@Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
@NotEmpty(message = "标识符不能为空")
private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举类
@Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
private Object data; // 例如说属性上报的 properties事件上报的 params
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备日志分页查询 Request VO")
@Data
public class IotDeviceLogPageReqVO extends PageParam {
@Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
@NotEmpty(message = "设备标识不能为空")
private String deviceKey;
@Schema(description = "消息类型", example = "property")
private String type; // 参见 IotDeviceMessageTypeEnum 枚举精准匹配
@Schema(description = "标识符", example = "temperature")
private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举模糊匹配
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备日志 Response VO")
@Data
public class IotDeviceLogRespVO {
@Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String id;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123")
private String productKey;
@Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
private String deviceKey;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
private String type;
@Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
private String identifier;
@Schema(description = "日志内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime reportTime;
@Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime ts;
}

View File

@ -20,9 +20,6 @@ public class IotDevicePropertyHistoryPageReqVO extends PageParam {
@NotNull(message = "设备编号不能为空")
private Long deviceId;
@Schema(description = "设备 Key", hidden = true)
private String deviceKey; // 非前端传递后端自己查询设置
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "属性标识符不能为空")
private String identifier;

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备认证信息 Response VO")
@Data
public class IotDeviceAuthInfoRespVO {
@Schema(description = "客户端 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123.device001")
@NotBlank(message = "客户端 ID 不能为空")
private String clientId;
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001&product123")
@NotBlank(message = "用户名不能为空")
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1a2b3c4d5e6f7890abcdef1234567890")
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 通过产品标识和设备名称列表获取设备 Request VO")
@Data
public class IotDeviceByProductKeyAndNamesReqVO {
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1de24640dfe")
@NotBlank(message = "产品标识不能为空")
private String productKey;
@Schema(description = "设备名称列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001,device002")
@NotEmpty(message = "设备名称列表不能为空")
private List<String> deviceNames;
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备 MQTT 连接参数 Response VO")
@Data
@ExcelIgnoreUnannotated
public class IotDeviceMqttConnectionParamsRespVO {
@Schema(description = "MQTT 客户端 ID", example = "24602")
@ExcelProperty("MQTT 客户端 ID")
private String mqttClientId;
@Schema(description = "MQTT 用户名", example = "芋艿")
@ExcelProperty("MQTT 用户名")
private String mqttUsername;
@Schema(description = "MQTT 密码")
@ExcelProperty("MQTT 密码")
private String mqttPassword;
}

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@ -20,10 +20,6 @@ public class IotDeviceRespVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
private Long id;
@Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("设备唯一标识符")
private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@ExcelProperty("设备名称")
private String deviceName;

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.Set;
@ -13,10 +12,6 @@ public class IotDeviceSaveReqVO {
@Schema(description = "设备编号", example = "177")
private Long id;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
@Size(max = 50, message = "设备编号长度不能超过 50 个字符")
private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.AUTO, example = "王五")
private String deviceName;

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备消息分页查询 Request VO")
@Data
public class IotDeviceMessagePageReqVO extends PageParam {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备编号不能为空")
private Long deviceId;
@Schema(description = "消息类型", example = "property")
@InEnum(IotDeviceMessageMethodEnum.class)
private String method;
@Schema(description = "是否上行", example = "true")
private Boolean upstream;
}

View File

@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备消息 Response VO")
@Data
public class IotDeviceMessageRespVO {
@Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String id;
@Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime reportTime;
@Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime ts;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
private Long deviceId;
@Schema(description = "服务编号", example = "server_123")
private String serverId;
@Schema(description = "是否上行消息", example = "true", examples = "false")
private Boolean upstream;
@Schema(description = "是否回复消息", example = "false", examples = "true")
private Boolean reply;
// ========== codec编解码字段 ==========
@Schema(description = "请求编号", example = "req_123")
private String requestId;
@Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.report")
private String method;
@Schema(description = "请求参数")
private Object params;
@Schema(description = "响应结果")
private Object data;
@Schema(description = "响应错误码", example = "200")
private Integer code;
@Schema(description = "响应提示", example = "操作成功")
private String msg;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备消息发送 Request VO") // 属性上报事件上报状态变更等
@Data
public class IotDeviceMessageSendReqVO {
@Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
@NotEmpty(message = "请求方法不能为空")
@InEnum(IotDeviceMessageMethodEnum.class)
private String method;
@Schema(description = "请求参数")
private Object params; // 例如说属性上报的 properties事件上报的 params
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
@NotNull(message = "设备编号不能为空")
private Long deviceId;
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
import com.fhs.core.trans.anno.Trans;
@ -89,7 +90,7 @@ public class IotOtaUpgradeRecordRespVO {
* 升级进度描述
* <p>
* 注意只记录设备最后一次的升级进度描述
* 如果想看历史记录可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
* 如果想看历史记录可以查看 {@link IotDeviceMessageDO} 设备日志
*/
@Schema(description = "升级进度描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private String description;

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.plugin.vo.config.PluginConfigImportReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config.PluginConfigPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config.PluginConfigRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config.PluginConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config.PluginConfigStatusReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;
import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 插件配置")
@RestController
@RequestMapping("/iot/plugin-config")
@Validated
public class PluginConfigController {
@Resource
private IotPluginConfigService pluginConfigService;
@PostMapping("/create")
@Operation(summary = "创建插件配置")
@PreAuthorize("@ss.hasPermission('iot:plugin-config:create')")
public CommonResult<Long> createPluginConfig(@Valid @RequestBody PluginConfigSaveReqVO createReqVO) {
return success(pluginConfigService.createPluginConfig(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新插件配置")
@PreAuthorize("@ss.hasPermission('iot:plugin-config:update')")
public CommonResult<Boolean> updatePluginConfig(@Valid @RequestBody PluginConfigSaveReqVO updateReqVO) {
pluginConfigService.updatePluginConfig(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除插件配置")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:plugin-config:delete')")
public CommonResult<Boolean> deletePluginConfig(@RequestParam("id") Long id) {
pluginConfigService.deletePluginConfig(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得插件配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:plugin-config:query')")
public CommonResult<PluginConfigRespVO> getPluginConfig(@RequestParam("id") Long id) {
IotPluginConfigDO pluginConfig = pluginConfigService.getPluginConfig(id);
return success(BeanUtils.toBean(pluginConfig, PluginConfigRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得插件配置分页")
@PreAuthorize("@ss.hasPermission('iot:plugin-config:query')")
public CommonResult<PageResult<PluginConfigRespVO>> getPluginConfigPage(@Valid PluginConfigPageReqVO pageReqVO) {
PageResult<IotPluginConfigDO> pageResult = pluginConfigService.getPluginConfigPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, PluginConfigRespVO.class));
}
@PostMapping("/upload-file")
@Operation(summary = "上传插件文件")
@PreAuthorize("@ss.hasPermission('iot:plugin-config:update')")
public CommonResult<Boolean> uploadFile(@Valid PluginConfigImportReqVO reqVO) {
pluginConfigService.uploadFile(reqVO.getId(), reqVO.getFile());
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "修改插件状态")
@PreAuthorize("@ss.hasPermission('iot:plugin-config:update')")
public CommonResult<Boolean> updatePluginConfigStatus(@Valid @RequestBody PluginConfigStatusReqVO reqVO) {
pluginConfigService.updatePluginStatus(reqVO.getId(), reqVO.getStatus());
return success(true);
}
}

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Schema(description = "管理后台 - IoT 插件上传 Request VO")
@Data
public class PluginConfigImportReqVO {
@Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
private Long id;
@Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "插件文件不能为空")
private MultipartFile file;
}

View File

@ -1,20 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - IoT 插件配置分页 Request VO")
@Data
public class PluginConfigPageReqVO extends PageParam {
@Schema(description = "插件名称", example = "http")
private String name;
@Schema(description = "状态", example = "1")
@InEnum(IotPluginStatusEnum.class)
private Integer status;
}

View File

@ -1,54 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 插件配置 Response VO")
@Data
public class PluginConfigRespVO {
@Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
private Long id;
@Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
private String pluginKey;
@Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
private String name;
@Schema(description = "描述", example = "你猜")
private String description;
@Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer deployType;
@Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
private String fileName;
@Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
private String version;
@Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer type;
@Schema(description = "设备插件协议类型")
private String protocol;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer status;
@Schema(description = "插件配置项描述信息")
private String configSchema;
@Schema(description = "插件配置信息")
private String config;
@Schema(description = "插件脚本")
private String script;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -1,56 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - IoT 插件配置新增/修改 Request VO")
@Data
public class PluginConfigSaveReqVO {
// TODO @haohao新增的字段有点多每个都需要哇
// TODO @haohao一些枚举字段需要加枚举校验例如说deployTypestatustype
@Schema(description = "主键编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
private Long id;
@Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
private String pluginKey;
@Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
private String name;
@Schema(description = "描述", example = "你猜")
private String description;
@Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer deployType;
@Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
private String fileName;
@Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
private String version;
@Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer type;
@Schema(description = "设备插件协议类型")
private String protocol;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
@InEnum(IotPluginStatusEnum.class)
private Integer status;
@Schema(description = "插件配置项描述信息")
private String configSchema;
@Schema(description = "插件配置信息")
private String config;
@Schema(description = "插件脚本")
private String script;
}

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.config;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - IoT 插件配置状态 Request VO")
@Data
public class PluginConfigStatusReqVO {
@Schema(description = "主键编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
private Long id;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
@InEnum(IotPluginStatusEnum.class)
private Integer status;
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.instance;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @haohao后续需要使用下
@Schema(description = "管理后台 - IoT 插件实例分页 Request VO")
@Data
public class PluginInstancePageReqVO extends PageParam {
@Schema(description = "插件主程序编号", example = "23738")
private String mainId;
@Schema(description = "插件id", example = "26498")
private Long pluginId;
@Schema(description = "插件主程序所在ip")
private String ip;
@Schema(description = "插件主程序端口")
private Integer port;
@Schema(description = "心跳时间心路时间超过30秒需要剔除")
private Long heartbeatAt;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.instance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
// TODO @haohao后续需要使用下
@Schema(description = "管理后台 - IoT 插件实例 Response VO")
@Data
public class PluginInstanceRespVO {
@Schema(description = "主键编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23864")
private Long id;
@Schema(description = "插件主程序id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23738")
private String mainId;
@Schema(description = "插件id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26498")
private Long pluginId;
@Schema(description = "插件主程序所在ip", requiredMode = Schema.RequiredMode.REQUIRED)
private String ip;
@Schema(description = "插件主程序端口", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer port;
@Schema(description = "心跳时间心路时间超过30秒需要剔除", requiredMode = Schema.RequiredMode.REQUIRED)
private Long heartbeatAt;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -84,6 +84,27 @@ public class IotProductController {
@PreAuthorize("@ss.hasPermission('iot:product:query')")
public CommonResult<IotProductRespVO> getProduct(@RequestParam("id") Long id) {
IotProductDO product = productService.getProduct(id);
if (product == null) {
return success(null);
}
// 拼接数据
IotProductCategoryDO category = categoryService.getProductCategory(product.getCategoryId());
return success(BeanUtils.toBean(product, IotProductRespVO.class, bean -> {
if (category != null) {
bean.setCategoryName(category.getName());
}
}));
}
@GetMapping("/get-by-key")
@Operation(summary = "通过 ProductKey 获得产品")
@Parameter(name = "productKey", description = "产品Key", required = true, example = "abc123")
@PreAuthorize("@ss.hasPermission('iot:product:query')")
public CommonResult<IotProductRespVO> getProductByKey(@RequestParam("productKey") String productKey) {
IotProductDO product = productService.getProductByProductKey(productKey);
if (product == null) {
return success(null);
}
// 拼接数据
IotProductCategoryDO category = categoryService.getProductCategory(product.getCategoryId());
return success(BeanUtils.toBean(product, IotProductRespVO.class, bean -> {

View File

@ -61,24 +61,10 @@ public class IotProductRespVO {
@DictFormat(DictTypeConstants.NET_TYPE)
private Integer netType;
@Schema(description = "接入网关协议", example = "2")
@ExcelProperty(value = "接入网关协议", converter = DictConvert.class)
@DictFormat(DictTypeConstants.PROTOCOL_TYPE)
private Integer protocolType;
@Schema(description = "协议编号(脚本解析 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "13177")
@ExcelProperty("协议编号(脚本解析 id")
private Long protocolId;
@Schema(description = "数据格式")
@Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty(value = "数据格式", converter = DictConvert.class)
@DictFormat(DictTypeConstants.DATA_FORMAT)
private Integer dataFormat;
@Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "数据校验级别", converter = DictConvert.class)
@DictFormat(DictTypeConstants.VALIDATE_TYPE)
private Integer validateType;
@DictFormat(DictTypeConstants.CODEC_TYPE)
private String codecType;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")

View File

@ -1,7 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.product.*;
import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
@ -43,18 +44,8 @@ public class IotProductSaveReqVO {
@InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
private Integer netType;
@Schema(description = "接入网关协议", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotProtocolTypeEnum.class, message = "接入网关协议必须是 {value}")
private Integer protocolType;
@Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotDataFormatEnum.class, message = "数据格式必须是 {value}")
@NotNull(message = "数据格式不能为空")
private Integer dataFormat;
@Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotValidateTypeEnum.class, message = "数据校验级别必须是 {value}")
@NotNull(message = "数据校验级别不能为空")
private Integer validateType;
@Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotEmpty(message = "数据格式不能为空")
private String codecType;
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@ -17,7 +18,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - IoT 数据桥梁")
@RestController
@ -69,4 +73,12 @@ public class IotDataBridgeController {
return success(BeanUtils.toBean(pageResult, IotDataBridgeRespVO.class));
}
}
@GetMapping("/simple-list")
@Operation(summary = "获取数据桥梁的精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<IotDataBridgeRespVO>> getSimpleDataBridgeList() {
List<IotDataBridgeDO> list = dataBridgeService.getDataBridgeList(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, dataBridge -> // 只返回 idname 字段
new IotDataBridgeRespVO().setId(dataBridge.getId()).setName(dataBridge.getName())));
}
}

View File

@ -1,15 +1,26 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.rule.vo.scene.IotRuleScenePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import cn.iocoder.yudao.module.iot.service.rule.IotRuleSceneService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@Tag(name = "管理后台 - IoT 规则场景")
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 场景联动")
@RestController
@RequestMapping("/iot/rule-scene")
@Validated
@ -18,6 +29,47 @@ public class IotRuleSceneController {
@Resource
private IotRuleSceneService ruleSceneService;
@PostMapping("/create")
@Operation(summary = "创建场景联动")
@PreAuthorize("@ss.hasPermission('iot:rule-scene:create')")
public CommonResult<Long> createRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO createReqVO) {
return success(ruleSceneService.createRuleScene(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新场景联动")
@PreAuthorize("@ss.hasPermission('iot:rule-scene:update')")
public CommonResult<Boolean> updateRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO updateReqVO) {
ruleSceneService.updateRuleScene(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除场景联动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:rule-scene:delete')")
public CommonResult<Boolean> deleteRuleScene(@RequestParam("id") Long id) {
ruleSceneService.deleteRuleScene(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得场景联动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:rule-scene:query')")
public CommonResult<IotRuleSceneRespVO> getRuleScene(@RequestParam("id") Long id) {
IotRuleSceneDO ruleScene = ruleSceneService.getRuleScene(id);
return success(BeanUtils.toBean(ruleScene, IotRuleSceneRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得场景联动分页")
@PreAuthorize("@ss.hasPermission('iot:rule-scene:query')")
public CommonResult<PageResult<IotRuleSceneRespVO>> getRuleScenePage(@Valid IotRuleScenePageReqVO pageReqVO) {
PageResult<IotRuleSceneDO> pageResult = ruleSceneService.getRuleScenePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotRuleSceneRespVO.class));
}
@GetMapping("/test")
@PermitAll
public void test() {

View File

@ -18,7 +18,7 @@ import lombok.Data;
@JsonSubTypes({
@JsonSubTypes.Type(value = IotDataBridgeHttpConfig.class, name = "1"),
@JsonSubTypes.Type(value = IotDataBridgeMqttConfig.class, name = "10"),
@JsonSubTypes.Type(value = IotDataBridgeRedisStreamMQConfig.class, name = "21"),
@JsonSubTypes.Type(value = IotDataBridgeRedisStreamConfig.class, name = "21"),
@JsonSubTypes.Type(value = IotDataBridgeRocketMQConfig.class, name = "30"),
@JsonSubTypes.Type(value = IotDataBridgeRabbitMQConfig.class, name = "31"),
@JsonSubTypes.Type(value = IotDataBridgeKafkaMQConfig.class, name = "32"),

View File

@ -2,14 +2,13 @@ package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.databridge.config;
import lombok.Data;
// TODO @puhui999MQ 可以去掉哈stream 更精准
/**
* IoT Redis Stream 配置 {@link IotDataBridgeAbstractConfig} 实现类
*
* @author HUIHUI
*/
@Data
public class IotDataBridgeRedisStreamMQConfig extends IotDataBridgeAbstractConfig {
public class IotDataBridgeRedisStreamConfig extends IotDataBridgeAbstractConfig {
/**
* Redis 服务器地址

View File

@ -1,2 +0,0 @@
// TODO @芋艿占位
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo;

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - IoT 场景联动分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IotRuleScenePageReqVO extends PageParam {
@Schema(description = "场景名称", example = "赵六")
private String name;
@Schema(description = "场景描述", example = "你猜")
private String description;
@Schema(description = "场景状态", example = "1")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - IoT 场景联动 Response VO")
@Data
public class IotRuleSceneRespVO {
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
private Long id;
@Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
private String name;
@Schema(description = "场景描述", example = "你猜")
private String description;
@Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<IotRuleSceneDO.TriggerConfig> triggers;
@Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<IotRuleSceneDO.ActionConfig> actions;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO")
@Data
public class IotRuleSceneSaveReqVO {
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
private Long id;
@Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotEmpty(message = "场景名称不能为空")
private String name;
@Schema(description = "场景描述", example = "你猜")
private String description;
@Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "场景状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "触发器数组不能为空")
private List<IotRuleSceneDO.TriggerConfig> triggers;
@Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "执行器数组不能为空")
private List<IotRuleSceneDO.ActionConfig> actions;
}

View File

@ -4,9 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsSummaryRespVO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDeviceLogService;
import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import io.swagger.v3.oas.annotations.Operation;

View File

@ -174,8 +174,7 @@ GET {{baseUrl}}/iot/product-thing-model/get?id=67
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
### 请求 /iot/product-thing-model/list-by-product-id 接口 => 成功
GET {{baseUrl}}/iot/product-thing-model/list-by-product-id?productId=1001
### 请求 /iot/product-thing-model/get-tsl 接口 => 成功
GET {{baseUrl}}/iot/product-thing-model/get-tsl?productId=1001
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}

View File

@ -1,13 +1,13 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.thingmodel.vo.IotThingModelListReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
@Tag(name = "管理后台 - IoT 产品物模型")
@RestController
@ -64,16 +66,31 @@ public class IotThingModelController {
return success(BeanUtils.toBean(thingModel, IotThingModelRespVO.class));
}
@GetMapping("/list-by-product-id")
@Operation(summary = "获得产品物模型")
@Parameter(name = "productId", description = "产品ID", required = true, example = "1024")
@GetMapping("/get-tsl")
@Operation(summary = "获得产品物模型 TSL")
@Parameter(name = "productId", description = "产品 ID", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
public CommonResult<List<IotThingModelRespVO>> getThingModelListByProductId(@RequestParam("productId") Long productId) {
List<IotThingModelDO> list = thingModelService.getThingModelListByProductId(productId);
return success(BeanUtils.toBean(list, IotThingModelRespVO.class));
public CommonResult<IotThingModelTSLRespVO> getThingModelTsl(@RequestParam("productId") Long productId) {
IotThingModelTSLRespVO tslRespVO = new IotThingModelTSLRespVO();
// TODO @puhui999是不是要先查询产品哈原因是万一没配置物模型但是产品已经有了
// 1. 获得产品所有物模型定义
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductId(productId);
if (CollUtil.isEmpty(thingModels)) {
return success(tslRespVO);
}
// 2. 设置公共部分参数
IotThingModelDO thingModel = thingModels.get(0);
tslRespVO.setProductId(thingModel.getProductId()).setProductKey(thingModel.getProductKey());
tslRespVO.setProperties(convertList(filterList(thingModels, item ->
ObjUtil.equal(IotThingModelTypeEnum.PROPERTY.getType(), item.getType())), IotThingModelDO::getProperty));
tslRespVO.setServices(convertList(filterList(thingModels, item ->
ObjUtil.equal(IotThingModelTypeEnum.SERVICE.getType(), item.getType())), IotThingModelDO::getService));
tslRespVO.setEvents(convertList(filterList(thingModels, item ->
ObjUtil.equal(IotThingModelTypeEnum.EVENT.getType(), item.getType())), IotThingModelDO::getEvent));
return success(tslRespVO);
}
// TODO @puhui @supergetThingModelListByProductId getThingModelListByProductId 可以融合么
@GetMapping("/list")
@Operation(summary = "获得产品物模型列表")
@PreAuthorize("@ss.hasPermission('iot:thing-model:query')")

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -16,18 +20,17 @@ import java.util.List;
@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段从而避免重复
public class ThingModelArrayDataSpecs extends ThingModelDataSpecs {
/**
* 数组中的元素个数
*/
@NotNull(message = "数组元素个数不能为空")
private Integer size;
/**
* 数组中的元素的数据类型可选值structintfloatdouble text
*/
@NotEmpty(message = "数组元素的数据类型不能为空")
@Pattern(regexp = "^(struct|int|float|double|text)$", message = "数组元素的数据类型必须为struct、int、float、double 或 text")
private String childDataType;
/**
* 数据类型childDataType为列表型 struct 的数据规范存储在 dataSpecsList
* 此时 struct 取值范围为intfloatdoubletextdateenumbool
*/
@Valid
private List<ThingModelDataSpecs> dataSpecsList;
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -16,16 +19,12 @@ import lombok.EqualsAndHashCode;
@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段从而避免重复
public class ThingModelBoolOrEnumDataSpecs extends ThingModelDataSpecs {
// TODO @puhui999要不写下参数校验这样注释可以简洁一点
/**
* 枚举项的名称
* 可包含中文大小写英文字母数字下划线_和短划线-
* 必须以中文英文字母或数字开头长度不超过 20 个字符
*/
@NotEmpty(message = "枚举项的名称不能为空")
@Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9][\\u4e00-\\u9fa5a-zA-Z0-9_-]{0,19}$",
message = "枚举项的名称只能包含中文、大小写英文字母、数字、下划线和短划线,必须以中文、英文字母或数字开头,长度不超过 20 个字符")
private String name;
/**
* 枚举值
*/
@NotNull(message = "枚举值不能为空")
private Integer value;
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.Max;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -20,6 +21,7 @@ public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
* 数据长度单位为字节取值不能超过 2048
* dataType text 需传入该参数
*/
@Max(value = 2048, message = "数据长度不能超过 2048")
private Integer length;
/**
* 默认值可选参数用于存储默认值

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -18,18 +20,21 @@ public class ThingModelNumericDataSpec extends ThingModelDataSpecs {
/**
* 最大值需转为字符串类型值必须与 dataType 类型一致
* 例如 dataType int 取值为 "200"而不是 200
*/
@NotEmpty(message = "最大值不能为空")
@Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最大值必须为数值类型")
private String max;
/**
* 最小值需转为字符串类型值必须与 dataType 类型一致
* 例如 dataType int 取值为 "0"而不是 0
*/
@NotEmpty(message = "最小值不能为空")
@Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最小值必须为数值类型")
private String min;
/**
* 步长需转为字符串类型值必须与 dataType 类型一致
* 例如 dataType int 取值为 "10"而不是 10
*/
@NotEmpty(message = "步长不能为空")
@Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "步长必须为数值类型")
private String step;
/**
* 精度 dataType float double 时可选传入

View File

@ -1,7 +1,11 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -17,35 +21,36 @@ import java.util.List;
@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段从而避免重复
public class ThingModelStructDataSpecs extends ThingModelDataSpecs {
/**
* 属性标识符
*/
@NotEmpty(message = "属性标识符不能为空")
@Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]{0,31}$", message = "属性标识符只能由字母、数字和下划线组成,必须以字母开头,长度不超过 32 个字符")
private String identifier;
/**
* 属性名称
*/
@NotEmpty(message = "属性名称不能为空")
private String name;
/**
* 云端可以对该属性进行的操作类型
*
* 枚举 {@link IotThingModelAccessModeEnum}
*/
@NotEmpty(message = "操作类型不能为空")
@InEnum(IotThingModelAccessModeEnum.class)
private String accessMode;
/**
* 是否是标准品类的必选服务
*/
private Boolean required;
/**
* struct 数据的数据类型
*/
@NotEmpty(message = "数据类型不能为空")
@Pattern(regexp = "^(int|float|double|text|date|enum|bool)$", message = "数据类型必须为int、float、double、text、date、enum、bool")
private String childDataType;
/**
* 数据类型dataType为非列表型intfloatdoubletextdatearray的数据规范存储在 dataSpecs
*/
@Valid
private ThingModelDataSpecs dataSpecs;
/**
* 数据类型dataType为列表型enumboolstruct的数据规范存储在 dataSpecsList
*/
@Valid
private List<ThingModelDataSpecs> dataSpecsList;
}

View File

@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -12,18 +10,15 @@ import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 产品物模型 Response VO")
@Data
@ExcelIgnoreUnannotated
public class IotThingModelRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
@ExcelProperty("产品ID")
private Long id;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long productId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor")
@ExcelProperty("产品标识")
private String productKey;
@Schema(description = "功能标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
@ -48,7 +43,6 @@ public class IotThingModelRespVO {
private ThingModelService service;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - IoT 产品物模型 TSL Response VO")
@Data
public class IotThingModelTSLRespVO {
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long productId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor")
private String productKey;
@Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ThingModelProperty> properties;
@Schema(description = "服务列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ThingModelEvent> events;
@Schema(description = "事件列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ThingModelService> services;
}

View File

@ -1,112 +0,0 @@
### 请求 /iot/think-model-function/create 接口 => 成功
POST {{baseUrl}}/iot/think-model-function/create
Content-Type: application/json
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
"productId": 1001,
"productKey": "smart-sensor-001",
"identifier": "Temperature",
"name": "温度",
"description": "当前温度值",
"type": 1,
"property": {
"identifier": "Temperature",
"name": "温度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": -10.0,
"max": 100.0,
"step": 0.1,
"unit": "℃"
}
},
"description": "当前温度值"
}
}
### 请求 /iot/think-model-function/create 接口 => 成功
POST {{baseUrl}}/iot/think-model-function/create
Content-Type: application/json
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
"productId": 1001,
"productKey": "smart-sensor-001",
"identifier": "Humidity",
"name": "湿度",
"description": "当前湿度值",
"type": 1,
"property": {
"identifier": "Humidity",
"name": "湿度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": 0.0,
"max": 100.0,
"step": 0.1,
"unit": "%"
}
},
"description": "当前湿度值"
}
}
### 请求 /iot/think-model-function/update 接口 => 成功
PUT {{baseUrl}}/iot/think-model-function/update
Content-Type: application/json
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
{
"id": 11,
"productId": 1001,
"productKey": "smart-sensor-001",
"identifier": "Temperature",
"name": "温度",
"description": "当前温度值",
"type": 1,
"property": {
"identifier": "Temperature",
"name": "温度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": -111.0,
"max": 222.0,
"step": 0.1,
"unit": "℃"
}
},
"description": "当前温度值"
}
}
### 请求 /iot/think-model-function/delete 接口 => 成功
DELETE {{baseUrl}}/iot/think-model-function/delete?id=7
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
### 请求 /iot/think-model-function/get 接口 => 成功
GET {{baseUrl}}/iot/think-model-function/get?id=10
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}
### 请求 /iot/think-model-function/list-by-product-id 接口 => 成功
GET {{baseUrl}}/iot/think-model-function/list-by-product-id?productId=1001
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}

View File

@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -33,12 +33,6 @@ public class IotDeviceDO extends TenantBaseDO {
*/
@TableId
private Long id;
/**
* 设备唯一标识符全局唯一用于识别设备
*
* 类似阿里云 <a href="https://help.aliyun.com/zh/iot/developer-reference/api-querydeviceinfo">QueryDeviceInfo</a> IotInstanceId
*/
private String deviceKey;
/**
* 设备名称在产品内唯一用于标识设备
*/
@ -120,23 +114,10 @@ public class IotDeviceDO extends TenantBaseDO {
*/
private String firmwareId;
// TODO @芋艿待定 003要不要增加 username目前 tl 阿里云之类的没有
/**
* 设备密钥用于设备认证需安全存储
* 设备密钥用于设备认证
*/
private String deviceSecret;
/**
* MQTT 客户端 ID
*/
private String mqttClientId;
/**
* MQTT 用户名
*/
private String mqttUsername;
/**
* MQTT 密码
*/
private String mqttPassword;
/**
* 认证类型如一机一密动态注册
*/

View File

@ -1,95 +0,0 @@
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.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.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IoT 设备日志数据 DO
*
* 目前使用 TDengine 存储
*
* @author alwayssuper
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceLogDO {
/**
* 日志编号
*
* 通过 {@link IdUtil#fastSimpleUUID()} 生成
*/
private String id;
/**
* 请求编号
*
* 对应 {@link IotDeviceMessage#getRequestId()} 字段
*/
private String requestId;
/**
* 产品标识
* <p>
* 关联 {@link IotProductDO#getProductKey()}
*/
private String productKey;
/**
* 设备名称
*
* 关联 {@link IotDeviceDO#getDeviceName()}
*/
private String deviceName;
/**
* 设备标识
* <p>
* 关联 {@link IotDeviceDO#getDeviceKey()}}
*/
private String deviceKey; // 非存储字段用于 TDengine TAG
/**
* 日志类型
*
* 枚举 {@link IotDeviceMessageTypeEnum}
*/
private String type;
/**
* 标识符
*
* 枚举 {@link IotDeviceMessageIdentifierEnum}
*/
private String identifier;
/**
* 数据内容
*
* 存储具体的消息数据内容通常是 JSON 格式
*/
private String content;
/**
* 响应码
*
* 目前只有 server 下行消息给 device 设备时才会有响应码
*/
private Integer code;
/**
* 上报时间戳
*/
private Long reportTime;
/**
* 时序时间
*/
private Long ts;
}

View File

@ -0,0 +1,101 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IoT 设备消息数据 DO
*
* 目前使用 TDengine 存储
*
* @author alwayssuper
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceMessageDO {
/**
* 消息编号
*/
private String id;
/**
* 上报时间戳
*/
private Long reportTime;
/**
* 存储时间戳
*/
private Long ts;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
*/
private Long deviceId;
/**
* 租户编号
*/
private Long tenantId;
/**
* 服务编号该消息由哪个 server 发送
*/
private String serverId;
/**
* 是否上行消息
*
* {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isUpstreamMessage(IotDeviceMessage)} 计算
* 计算并存储的目的方便计算多少条上行多少条下行
*/
private Boolean upstream;
/**
* 是否回复消息
*
* {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isReplyMessage(IotDeviceMessage)} 计算
* 计算并存储的目的方便计算多少条请求多少条回复
*/
private Boolean reply;
// ========== codec编解码字段 ==========
/**
* 请求编号
*
* 由设备生成对应阿里云 IoT Alink 协议中的 id华为云 IoTDA 协议的 request_id
*/
private String requestId;
/**
* 请求方法
*
* 枚举 {@link IotDeviceMessageMethodEnum}
* 例如说thing.property.report 属性上报
*/
private String method;
/**
* 请求参数
*
* 例如说属性上报的 properties事件上报的 params
*/
private Object params;
/**
* 响应结果
*/
private Object data;
/**
* 响应错误码
*/
private Integer code;
/**
* 响应提示
*/
private String msg;
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -77,7 +78,7 @@ public class IotOtaUpgradeRecordDO extends BaseDO {
* 升级进度描述
*
* 注意只记录设备最后一次的升级进度描述
* 如果想看历史记录可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
* 如果想看历史记录可以查看 {@link IotDeviceMessageDO} 设备日志
*/
private String description;
/**

View File

@ -1,93 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.plugin;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* IoT 插件配置 DO
*
* @author 芋道源码
*/
@TableName("iot_plugin_config")
@KeySequence("iot_plugin_config_seq")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotPluginConfigDO extends TenantBaseDO {
/**
* 主键 ID
*/
@TableId
private Long id;
/**
* 插件包标识符
*/
private String pluginKey;
/**
* 插件名称
*/
private String name;
/**
* 插件描述
*/
private String description;
/**
* 部署方式
* <p>
* 枚举 {@link IotPluginDeployTypeEnum}
*/
private Integer deployType;
// TODO @芋艿如果是外置的插件fileName version 的选择~
/**
* 插件包文件名
*/
private String fileName;
/**
* 插件版本
*/
private String version;
// TODO @芋艿type 字典的定义
/**
* 插件类型
* <p>
* 枚举 {@link IotPluginTypeEnum}
*/
private Integer type;
/**
* 设备插件协议类型
*/
// TODO @芋艿枚举字段
private String protocol;
// TODO @haohao这个字段是不是直接用 CommonStatus开启禁用然后插件实例那online 是否在线
/**
* 状态
* <p>
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
// TODO @芋艿configSchemaconfig 示例字段
/**
* 插件配置项描述信息
*/
private String configSchema;
/**
* 插件配置信息
*/
private String config;
// TODO @芋艿script 后续的使用
/**
* 插件脚本
*/
private String script;
}

View File

@ -1,70 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.plugin;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
* IoT 插件实例 DO
*
* @author 芋道源码
*/
@TableName("iot_plugin_instance")
@KeySequence("iot_plugin_instance_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotPluginInstanceDO extends TenantBaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 插件编号
* <p>
* 关联 {@link IotPluginConfigDO#getId()}
*/
private Long pluginId;
/**
* 插件进程编号
*
* 一般格式是hostIp@processId@${uuid}
*/
private String processId;
/**
* 插件实例所在 IP
*/
private String hostIp;
/**
* 设备下行端口
*/
private Integer downstreamPort;
/**
* 是否在线
*/
private Boolean online;
/**
* 在线时间
*/
private LocalDateTime onlineTime;
/**
* 离线时间
*/
private LocalDateTime offlineTime;
/**
* 心跳时间
*
* 目的心路时间超过一定时间后会被进行下线处理
*/
private LocalDateTime heartbeatTime;
}

View File

@ -71,28 +71,12 @@ public class IotProductDO extends TenantBaseDO {
private Integer netType;
/**
* 接入网关协议
* 数据格式编解码器类型
* <p>
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotProtocolTypeEnum}
* 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
*
* 目的用于 gateway-server 解析消息格式
*/
private Integer protocolType;
/**
* 协议编号
* <p>
* TODO 外键后续加
*/
private Long protocolId;
/**
* 数据格式
* <p>
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotDataFormatEnum}
*/
private Integer dataFormat;
/**
* 数据校验级别
* <p>
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotValidateTypeEnum}
*/
private Integer validateType;
private String codecType;
}

View File

@ -1,14 +1,17 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IoT 告警记录 DO

View File

@ -14,17 +14,20 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* IoT 规则场景场景联动 DO
* IoT 场景联动 DO
*
* @author 芋道源码
*/
@TableName("iot_rule_scene")
@TableName(value = "iot_rule_scene", autoResultMap = true)
@KeySequence("iot_rule_scene_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@Builder
@ -141,8 +144,17 @@ public class IotRuleSceneDO extends TenantBaseDO {
@Data
public static class TriggerConditionParameter {
// TODO @芋艿: identifier0 存事件和服务的 identifier 属性的情况 identifier0 就为 null 解决前端回显问题
// TODO @puhui999可以根据 TriggerCondition.type 判断是服务还是事件还是属性么
/**
* 标识符属性事件服务
* 标识符事件服务
*
* 关联 {@link IotThingModelDO#getIdentifier()}
*/
private String identifier0;
/**
* 标识符属性
*
* 关联 {@link IotThingModelDO#getIdentifier()}
*/

View File

@ -6,11 +6,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import javax.annotation.Nullable;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -50,11 +50,6 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
return selectCount(IotDeviceDO::getProductId, productId);
}
default IotDeviceDO selectByDeviceKey(String deviceKey) {
return selectOne(new LambdaQueryWrapper<IotDeviceDO>()
.apply("LOWER(device_key) = {0}", deviceKey.toLowerCase()));
}
default List<IotDeviceDO> selectListByDeviceType(Integer deviceType) {
return selectList(IotDeviceDO::getDeviceType, deviceType);
}
@ -77,6 +72,12 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
.geIfPresent(IotDeviceDO::getCreateTime, createTime));
}
default List<IotDeviceDO> selectByProductKeyAndDeviceNames(String productKey, Collection<String> deviceNames) {
return selectList(new LambdaQueryWrapperX<IotDeviceDO>()
.eq(IotDeviceDO::getProductKey, productKey)
.in(IotDeviceDO::getDeviceName, deviceNames));
}
/**
* 查询指定产品下各状态的设备数量
*
@ -93,4 +94,4 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
*/
List<Map<String, Object>> selectDeviceCountGroupByState();
}
}

Some files were not shown because too many files have changed in this diff Show More