mirror of
https://gitee.com/myxzgzs/boyue-vue-pro.git
synced 2025-08-08 16:32:46 +08:00
Merge branch 'feature/iot' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into origin/feature/iot
This commit is contained in:
commit
1ce9420a8d
@ -0,0 +1,2 @@
|
||||
http-plugin
|
||||
http-plugin@0.0.1
|
Binary file not shown.
@ -66,7 +66,9 @@ public class CrmReceivableDO extends BaseDO {
|
||||
*/
|
||||
private LocalDateTime returnTime;
|
||||
/**
|
||||
* 回款方式,关联枚举{@link CrmReceivableReturnTypeEnum}
|
||||
* 回款方式
|
||||
*
|
||||
* 枚举 {@link CrmReceivableReturnTypeEnum}
|
||||
*/
|
||||
private Integer returnType;
|
||||
/**
|
||||
|
@ -22,6 +22,7 @@
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
<!-- PF4J -->
|
||||
<!-- TODO 芋艿:这个依赖,要不要放在 api 包 -->
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
|
@ -7,6 +7,7 @@ import java.util.Map;
|
||||
* 服务注册表 - 插架模块使用,无法使用 Spring 注入
|
||||
*/
|
||||
public class ServiceRegistry {
|
||||
|
||||
private static final Map<Class<?>, Object> services = new HashMap<>();
|
||||
|
||||
/**
|
||||
@ -31,4 +32,5 @@ public class ServiceRegistry {
|
||||
public static <T> T getService(Class<T> serviceClass) {
|
||||
return (T) services.get(serviceClass);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.api;
|
||||
package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
/**
|
||||
* 设备数据 API
|
@ -14,7 +14,6 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
|
||||
ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
|
||||
ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
|
||||
ErrorCode PRODUCT_DEVICE_NOT_EXISTS = new ErrorCode(1_050_001_004, "产品设备类型不存在");
|
||||
|
||||
// ========== 产品物模型 1-050-002-000 ============
|
||||
ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
|
||||
|
@ -34,12 +34,10 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable {
|
||||
}
|
||||
|
||||
public static IotPluginDeployTypeEnum fromDeployType(Integer deployType) {
|
||||
for (IotPluginDeployTypeEnum value : values()) {
|
||||
if (value.getDeployType().equals(deployType)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return Arrays.stream(values())
|
||||
.filter(value -> value.getDeployType().equals(deployType))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public static boolean isValidDeployType(Integer deployType) {
|
||||
|
@ -34,12 +34,10 @@ public enum IotPluginStatusEnum implements IntArrayValuable {
|
||||
}
|
||||
|
||||
public static IotPluginStatusEnum fromState(Integer state) {
|
||||
for (IotPluginStatusEnum value : values()) {
|
||||
if (value.getStatus().equals(state)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return Arrays.stream(values())
|
||||
.filter(value -> value.getStatus().equals(state))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,14 +35,11 @@ public enum IotPluginTypeEnum implements IntArrayValuable {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
// TODO @haohao:可以使用 hutool 简化
|
||||
public static IotPluginTypeEnum fromType(Integer type) {
|
||||
for (IotPluginTypeEnum value : values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return Arrays.stream(values())
|
||||
.filter(value -> value.getType().equals(type))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public static boolean isValidType(Integer type) {
|
||||
|
@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.thingmodel;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IoT 数据定义的数据类型枚举类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IotDataSpecsDataTypeEnum {
|
||||
|
||||
INT("int"),
|
||||
FLOAT("float"),
|
||||
DOUBLE("double"),
|
||||
ENUM("enum"),
|
||||
BOOL("bool"),
|
||||
TEXT("text"),
|
||||
DATE("date"),
|
||||
STRUCT("struct"),
|
||||
ARRAY("array");
|
||||
|
||||
private final String dataType;
|
||||
|
||||
}
|
@ -4,13 +4,13 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IOT 访问方式枚举类
|
||||
* IOT 产品物模型属性读取类型枚举
|
||||
*
|
||||
* @author ahh
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IotProductThingModelAccessModeEnum {
|
||||
public enum IotThingModelAccessModeEnum {
|
||||
|
||||
READ_ONLY("r"),
|
||||
READ_WRITE("rw");
|
@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.thingmodel;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IOT 产品物模型参数是输入参数还是输出参数枚举
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IotThingModelParamDirectionEnum {
|
||||
|
||||
INPUT("input"), // 输入参数
|
||||
OUTPUT("output"); // 输出参数
|
||||
|
||||
private final String direction;
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.thingmodel;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IOT 产品物模型服务调用方式枚举
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IotThingModelServiceCallTypeEnum {
|
||||
|
||||
ASYNC("async"), // 异步调用
|
||||
SYNC("sync"); // 同步调用
|
||||
|
||||
private final String type;
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.thingmodel;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IOT 产品物模型事件类型枚举
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IotThingModelServiceEventTypeEnum {
|
||||
|
||||
INFO("info"), // 信息
|
||||
ALERT("alert"), // 告警
|
||||
ERROR("error"); // 故障
|
||||
|
||||
private final String type;
|
||||
|
||||
}
|
@ -13,13 +13,13 @@ import java.util.Arrays;
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IotProductThingModelTypeEnum implements IntArrayValuable {
|
||||
public enum IotThingModelTypeEnum implements IntArrayValuable {
|
||||
|
||||
PROPERTY(1, "属性"),
|
||||
SERVICE(2, "服务"),
|
||||
EVENT(3, "事件");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductThingModelTypeEnum::getType).toArray();
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotThingModelTypeEnum::getType).toArray();
|
||||
|
||||
/**
|
||||
* 类型
|
||||
@ -30,8 +30,8 @@ public enum IotProductThingModelTypeEnum implements IntArrayValuable {
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
public static IotProductThingModelTypeEnum valueOfType(Integer type) {
|
||||
for (IotProductThingModelTypeEnum value : values()) {
|
||||
public static IotThingModelTypeEnum valueOfType(Integer type) {
|
||||
for (IotThingModelTypeEnum value : values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
@ -25,13 +25,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-iot-plugin-api</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
@ -81,6 +74,7 @@
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
<!-- TODO @芋艿:可以放到 bom 里配置 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@ -15,11 +14,11 @@ import javax.annotation.Resource;
|
||||
public class DeviceDataApiImpl implements DeviceDataApi {
|
||||
|
||||
@Resource
|
||||
private IotDeviceDataService iotDeviceDataService;
|
||||
private IotDevicePropertyDataService deviceDataService;
|
||||
|
||||
@Override
|
||||
public void saveDeviceData(String productKey, String deviceName, String message) {
|
||||
iotDeviceDataService.saveDeviceData(productKey, deviceName, message);
|
||||
deviceDataService.saveDeviceData(productKey, deviceName, message);
|
||||
}
|
||||
|
||||
}
|
@ -1 +1,6 @@
|
||||
/**
|
||||
* 占位
|
||||
*
|
||||
* TODO 芋艿:后续删除
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.api;
|
@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -29,7 +29,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
public class IotDeviceDataController {
|
||||
|
||||
@Resource
|
||||
private IotDeviceDataService deviceDataService;
|
||||
private IotDevicePropertyDataService deviceDataService;
|
||||
|
||||
// TODO @浩浩:这里的 /latest-list,包括方法名。
|
||||
@GetMapping("/latest")
|
||||
|
@ -1,13 +1,14 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
|
||||
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.plugininfo.vo.PluginInfoPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoImportReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import cn.iocoder.yudao.module.iot.service.plugininfo.PluginInfoService;
|
||||
import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -16,11 +17,8 @@ import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 插件信息")
|
||||
@RestController
|
||||
@ -72,16 +70,11 @@ public class PluginInfoController {
|
||||
return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/update-jar",
|
||||
method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
|
||||
@Operation(summary = "上传Jar包")
|
||||
public CommonResult<Boolean> uploadJar(
|
||||
@RequestParam("id") Long id,
|
||||
@RequestParam("jar") MultipartFile file) throws Exception {
|
||||
if (file.isEmpty()) {
|
||||
throw exception(FILE_IS_EMPTY);
|
||||
}
|
||||
pluginInfoService.uploadJar(id, file);
|
||||
@PostMapping("/upload-file")
|
||||
@Operation(summary = "上传插件文件")
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-info:update')")
|
||||
public CommonResult<Boolean> uploadFile(@Valid PluginInfoImportReqVO reqVO) {
|
||||
pluginInfoService.uploadFile(reqVO.getId(), reqVO.getFile());
|
||||
return success(true);
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
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 PluginInfoImportReqVO {
|
||||
|
||||
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "插件文件不能为空")
|
||||
private MultipartFile file;
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 插件信息分页 Request VO")
|
||||
@Data
|
||||
public class PluginInfoPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "插件名称", example = "http")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private Integer status;
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
@ -16,9 +16,9 @@ public class PluginInfoRespVO {
|
||||
@ExcelProperty("主键 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "插件包 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
|
||||
@ExcelProperty("插件包 ID")
|
||||
private String pluginId;
|
||||
@Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
|
||||
@ExcelProperty("插件包标识符")
|
||||
private String pluginKey;
|
||||
|
||||
@Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
@ExcelProperty("插件名称")
|
||||
@ -34,7 +34,7 @@ public class PluginInfoRespVO {
|
||||
|
||||
@Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("插件包文件名")
|
||||
private String file;
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("插件版本")
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
@ -10,8 +10,8 @@ public class PluginInfoSaveReqVO {
|
||||
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
|
||||
private String pluginId;
|
||||
@Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
|
||||
private String pluginKey;
|
||||
|
||||
@Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
private String name;
|
||||
@ -23,7 +23,7 @@ public class PluginInfoSaveReqVO {
|
||||
private Integer deployType;
|
||||
|
||||
@Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String file;
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String version;
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
import lombok.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
@ -1,116 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.pf4j.spring.SpringPluginManager;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.security.PermitAll;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 插件 Controller 测试用例
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/iot/plugins")
|
||||
public class PluginController {
|
||||
|
||||
@Resource
|
||||
private SpringPluginManager springPluginManager;
|
||||
|
||||
@Value("${pf4j.pluginsDir}")
|
||||
private String pluginsDir;
|
||||
|
||||
/**
|
||||
* 上传插件 JAR 文件并加载插件
|
||||
*
|
||||
* @param file 上传的 JAR 文件
|
||||
* @return 上传结果
|
||||
*/
|
||||
@PermitAll
|
||||
@PostMapping("/upload")
|
||||
public ResponseEntity<String> uploadPlugin(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件为空");
|
||||
}
|
||||
|
||||
// 确保插件目录存在
|
||||
Path pluginsPath = Paths.get(pluginsDir);
|
||||
try {
|
||||
if (!Files.exists(pluginsPath)) {
|
||||
Files.createDirectories(pluginsPath);
|
||||
}
|
||||
|
||||
// 保存上传的 JAR 文件到插件目录
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename == null || (!filename.endsWith(".jar") && !filename.endsWith(".zip"))) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件不是 JAR 或 ZIP 文件");
|
||||
}
|
||||
|
||||
Path jarPath = pluginsPath.resolve(filename);
|
||||
|
||||
Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// 加载插件
|
||||
String pluginId = springPluginManager.loadPlugin(jarPath.toAbsolutePath());
|
||||
|
||||
// 启动插件
|
||||
springPluginManager.startPlugin(pluginId);
|
||||
|
||||
return ResponseEntity.ok("插件上传并加载成功");
|
||||
} catch (IOException e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传插件时发生错误: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("加载插件时发生错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定插件
|
||||
*
|
||||
* @param pluginId 插件 ID
|
||||
* @return 卸载结果
|
||||
*/
|
||||
@PermitAll
|
||||
@DeleteMapping("/unload/{pluginId}")
|
||||
public ResponseEntity<String> unloadPlugin(@PathVariable String pluginId) {
|
||||
if (springPluginManager.getPlugins().stream().noneMatch(plugin -> plugin.getDescriptor().getPluginId().equals(pluginId))) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("插件未加载: " + pluginId);
|
||||
}
|
||||
|
||||
springPluginManager.stopPlugin(pluginId);
|
||||
springPluginManager.unloadPlugin(pluginId);
|
||||
|
||||
// 删除插件 JAR 文件(可选)
|
||||
// PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||
// PluginDescriptor descriptor = plugin.getDescriptor();
|
||||
// Path jarPath = Paths.get(pluginsDir).resolve(descriptor.getPluginId() + ".jar");
|
||||
// Files.deleteIfExists(jarPath);
|
||||
|
||||
return ResponseEntity.ok("插件卸载成功: " + pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有已加载的插件
|
||||
*
|
||||
* @return 插件列表
|
||||
*/
|
||||
@PermitAll
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<List<String>> listPlugins() {
|
||||
List<String> plugins = springPluginManager.getPlugins().stream()
|
||||
.map(plugin -> plugin.getDescriptor().getPluginId())
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(plugins);
|
||||
}
|
||||
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
|
||||
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 PluginInfoPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "插件包 ID ", example = "24627")
|
||||
private String pluginId;
|
||||
|
||||
@Schema(description = "插件名称", example = "赵六")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "部署方式", example = "2")
|
||||
private Integer deployType;
|
||||
|
||||
@Schema(description = "插件包文件名")
|
||||
private String file;
|
||||
|
||||
@Schema(description = "插件版本")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "插件类型", example = "2")
|
||||
@InEnum(IotPluginTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "设备插件协议类型")
|
||||
private String protocol;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "插件配置项描述信息")
|
||||
private String configSchema;
|
||||
|
||||
@Schema(description = "插件配置信息")
|
||||
private String config;
|
||||
|
||||
@Schema(description = "插件脚本")
|
||||
private String script;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import jakarta.validation.*;
|
||||
import jakarta.servlet.http.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
|
||||
import cn.iocoder.yudao.module.iot.service.plugininstance.PluginInstanceService;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 插件实例")
|
||||
@RestController
|
||||
@RequestMapping("/iot/plugin-instance")
|
||||
@Validated
|
||||
public class PluginInstanceController {
|
||||
|
||||
@Resource
|
||||
private PluginInstanceService pluginInstanceService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建IoT 插件实例")
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:create')")
|
||||
public CommonResult<Long> createPluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO createReqVO) {
|
||||
return success(pluginInstanceService.createPluginInstance(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新IoT 插件实例")
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:update')")
|
||||
public CommonResult<Boolean> updatePluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO updateReqVO) {
|
||||
pluginInstanceService.updatePluginInstance(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除IoT 插件实例")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:delete')")
|
||||
public CommonResult<Boolean> deletePluginInstance(@RequestParam("id") Long id) {
|
||||
pluginInstanceService.deletePluginInstance(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得IoT 插件实例")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
|
||||
public CommonResult<PluginInstanceRespVO> getPluginInstance(@RequestParam("id") Long id) {
|
||||
PluginInstanceDO pluginInstance = pluginInstanceService.getPluginInstance(id);
|
||||
return success(BeanUtils.toBean(pluginInstance, PluginInstanceRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得IoT 插件实例分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
|
||||
public CommonResult<PageResult<PluginInstanceRespVO>> getPluginInstancePage(@Valid PluginInstancePageReqVO pageReqVO) {
|
||||
PageResult<PluginInstanceDO> pageResult = pluginInstanceService.getPluginInstancePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, PluginInstanceRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出IoT 插件实例 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportPluginInstanceExcel(@Valid PluginInstancePageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<PluginInstanceDO> list = pluginInstanceService.getPluginInstancePage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "IoT 插件实例.xls", "数据", PluginInstanceRespVO.class,
|
||||
BeanUtils.toBean(list, PluginInstanceRespVO.class));
|
||||
}
|
||||
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
|
||||
|
||||
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.IotProductThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.convert.thingmodel.IotProductThingModelConvert;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
|
||||
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 java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 产品物模型")
|
||||
@RestController
|
||||
@RequestMapping("/iot/product-thing-model")
|
||||
@Validated
|
||||
public class IotProductThingModelController {
|
||||
|
||||
@Resource
|
||||
private IotProductThingModelService thingModelService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建产品物模型")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-thing-model:create')")
|
||||
public CommonResult<Long> createProductThingModel(@Valid @RequestBody IotProductThingModelSaveReqVO createReqVO) {
|
||||
return success(thingModelService.createProductThingModel(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新产品物模型")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-thing-model:update')")
|
||||
public CommonResult<Boolean> updateProductThingModel(@Valid @RequestBody IotProductThingModelSaveReqVO updateReqVO) {
|
||||
thingModelService.updateProductThingModel(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除产品物模型")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-thing-model:delete')")
|
||||
public CommonResult<Boolean> deleteProductThingModel(@RequestParam("id") Long id) {
|
||||
thingModelService.deleteProductThingModel(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得产品物模型")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
|
||||
public CommonResult<IotProductThingModelRespVO> getProductThingModel(@RequestParam("id") Long id) {
|
||||
IotProductThingModelDO thingModel = thingModelService.getProductThingModel(id);
|
||||
return success(IotProductThingModelConvert.INSTANCE.convert(thingModel));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-product-id")
|
||||
@Operation(summary = "获得产品物模型")
|
||||
@Parameter(name = "productId", description = "产品ID", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
|
||||
public CommonResult<List<IotProductThingModelRespVO>> getProductThingModelListByProductId(@RequestParam("productId") Long productId) {
|
||||
List<IotProductThingModelDO> list = thingModelService.getProductThingModelListByProductId(productId);
|
||||
return success(IotProductThingModelConvert.INSTANCE.convertList(list));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得产品物模型分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
|
||||
public CommonResult<PageResult<IotProductThingModelRespVO>> getProductThingModelPage(@Valid IotProductThingModelPageReqVO pageReqVO) {
|
||||
PageResult<IotProductThingModelDO> pageResult = thingModelService.getProductThingModelPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotProductThingModelRespVO.class));
|
||||
}
|
||||
|
||||
}
|
@ -25,8 +25,7 @@ Authorization: Bearer {{token}}
|
||||
"defaultValue": "30",
|
||||
"unit": "%",
|
||||
"unitName": "百分比"
|
||||
},
|
||||
"description": "当前温度值"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +45,6 @@ Authorization: Bearer {{token}}
|
||||
"property": {
|
||||
"identifier": "switch",
|
||||
"name": "开关",
|
||||
"description": "温度计开关",
|
||||
"accessMode": "rw",
|
||||
"required": true,
|
||||
"dataType": "bool",
|
||||
@ -81,7 +79,6 @@ Authorization: Bearer {{token}}
|
||||
"property": {
|
||||
"identifier": "argb",
|
||||
"name": "温度计 argb 颜色",
|
||||
"description": "温度计 argb 颜色",
|
||||
"accessMode": "rw",
|
||||
"required": true,
|
||||
"dataType": "array",
|
||||
@ -93,7 +90,6 @@ Authorization: Bearer {{token}}
|
||||
{
|
||||
"identifier": "switch",
|
||||
"name": "开关",
|
||||
"description": "温度计开关",
|
||||
"accessMode": "rw",
|
||||
"required": true,
|
||||
"dataType": "struct",
|
||||
@ -126,8 +122,7 @@ Authorization: Bearer {{token}}
|
||||
"defaultValue": "30",
|
||||
"unit": "%",
|
||||
"unitName": "百分比"
|
||||
},
|
||||
"description": "当前温度值"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -151,7 +146,6 @@ Authorization: Bearer {{token}}
|
||||
"property": {
|
||||
"identifier": "switch",
|
||||
"name": "开关",
|
||||
"description": "温度计开关",
|
||||
"accessMode": "r",
|
||||
"required": true,
|
||||
"dataType": "bool",
|
||||
@ -176,7 +170,7 @@ tenant-id: {{adminTenentId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 请求 /iot/product-thing-model/get 接口 => 成功
|
||||
GET {{baseUrl}}/iot/product-thing-model/get?id=40
|
||||
GET {{baseUrl}}/iot/product-thing-model/get?id=67
|
||||
tenant-id: {{adminTenentId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
@ -0,0 +1,84 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
|
||||
|
||||
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.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.convert.thingmodel.IotThingModelConvert;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
|
||||
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 java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 产品物模型")
|
||||
@RestController
|
||||
@RequestMapping("/iot/thing-model")
|
||||
@Validated
|
||||
public class IotThingModelController {
|
||||
|
||||
@Resource
|
||||
private IotThingModelService thingModelService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建产品物模型")
|
||||
@PreAuthorize("@ss.hasPermission('iot:thing-model:create')")
|
||||
public CommonResult<Long> createThingModel(@Valid @RequestBody IotThingModelSaveReqVO createReqVO) {
|
||||
return success(thingModelService.createThingModel(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新产品物模型")
|
||||
@PreAuthorize("@ss.hasPermission('iot:thing-model:update')")
|
||||
public CommonResult<Boolean> updateThingModel(@Valid @RequestBody IotThingModelSaveReqVO updateReqVO) {
|
||||
thingModelService.updateThingModel(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除产品物模型")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:thing-model:delete')")
|
||||
public CommonResult<Boolean> deleteThingModel(@RequestParam("id") Long id) {
|
||||
thingModelService.deleteThingModel(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得产品物模型")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
|
||||
public CommonResult<IotThingModelRespVO> getThingModel(@RequestParam("id") Long id) {
|
||||
IotThingModelDO thingModel = thingModelService.getThingModel(id);
|
||||
return success(IotThingModelConvert.INSTANCE.convert(thingModel));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-product-id")
|
||||
@Operation(summary = "获得产品物模型")
|
||||
@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(IotThingModelConvert.INSTANCE.convertList(list));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得产品物模型分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
|
||||
public CommonResult<PageResult<IotThingModelRespVO>> getThingModelPage(@Valid IotThingModelPageReqVO pageReqVO) {
|
||||
PageResult<IotThingModelDO> pageResult = thingModelService.getProductThingModelPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotThingModelRespVO.class));
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,15 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceEventTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型中的事件
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class ThingModelEvent {
|
||||
|
||||
@ -16,17 +22,24 @@ public class ThingModelEvent {
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 事件描述
|
||||
* 是否是标准品类的必选事件
|
||||
*/
|
||||
private String description;
|
||||
|
||||
private Boolean required;
|
||||
/**
|
||||
* 事件类型
|
||||
*
|
||||
* "info"、"alert"、"error"
|
||||
* 枚举 {@link IotThingModelServiceEventTypeEnum}
|
||||
*/
|
||||
private String type;
|
||||
private List<ThingModelArgument> outputData;
|
||||
/**
|
||||
* 事件的输出参数
|
||||
*
|
||||
* 输出参数定义事件调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
|
||||
*/
|
||||
private List<ThingModelParam> outputParams;
|
||||
/**
|
||||
* 标识设备需要执行的具体操作
|
||||
*/
|
||||
private String method;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelParamDirectionEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IOT 产品物模型中的参数
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class ThingModelParam {
|
||||
|
||||
/**
|
||||
* 参数标识符
|
||||
*/
|
||||
private String identifier;
|
||||
/**
|
||||
* 参数名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 用于区分输入或输出参数
|
||||
*
|
||||
* 枚举 {@link IotThingModelParamDirectionEnum}
|
||||
*/
|
||||
private String direction;
|
||||
/**
|
||||
* 参数的序号。从 0 开始排序,且不能重复。
|
||||
*
|
||||
* TODO 考虑要不要序号,感觉是要的, 先留一手看看
|
||||
*/
|
||||
private Integer paraOrder;
|
||||
/**
|
||||
* 参数值的数据类型,与 dataSpecs 的 dataType 保持一致
|
||||
*/
|
||||
private String dataType;
|
||||
/**
|
||||
* 参数值的数据类型(dataType)为非列表型(int、float、double、text、date、array)的数据规范存储在 dataSpecs 中
|
||||
*/
|
||||
private ThingModelDataSpecs dataSpecs;
|
||||
/**
|
||||
* 参数值的数据类型(dataType)为列表型(enum、bool、struct)的数据规范存储在 dataSpecsList 中
|
||||
*/
|
||||
private List<ThingModelDataSpecs> dataSpecsList;
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
@ -24,13 +24,10 @@ public class ThingModelProperty {
|
||||
* 属性名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 属性描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 云端可以对该属性进行的操作类型
|
||||
* 关联枚举 {@link IotProductThingModelAccessModeEnum}
|
||||
*
|
||||
* 枚举 {@link IotThingModelAccessModeEnum}
|
||||
*/
|
||||
private String accessMode;
|
||||
/**
|
||||
@ -42,6 +39,8 @@ public class ThingModelProperty {
|
||||
private Boolean required;
|
||||
/**
|
||||
* 数据类型,与 dataSpecs 的 dataType 保持一致
|
||||
*
|
||||
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum}
|
||||
*/
|
||||
private String dataType;
|
||||
/**
|
||||
|
@ -1,9 +1,15 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceCallTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型中的服务
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class ThingModelService {
|
||||
|
||||
@ -16,18 +22,30 @@ public class ThingModelService {
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 服务描述
|
||||
* 是否是标准品类的必选服务
|
||||
*/
|
||||
private String description;
|
||||
|
||||
private Boolean required;
|
||||
/**
|
||||
* 调用类型
|
||||
*
|
||||
* "sync"、"async"
|
||||
* 枚举 {@link IotThingModelServiceCallTypeEnum}
|
||||
*/
|
||||
private String callType;
|
||||
private List<ThingModelArgument> inputData;
|
||||
private List<ThingModelArgument> outputData;
|
||||
/**
|
||||
* 服务的输入参数
|
||||
*
|
||||
* 输入参数定义服务调用时所需提供的信息,用于控制设备行为或执行特定任务
|
||||
*/
|
||||
private List<ThingModelParam> inputParams;
|
||||
/**
|
||||
* 服务的输出参数
|
||||
*
|
||||
* 输出参数定义服务调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
|
||||
*/
|
||||
private List<ThingModelParam> outputParams;
|
||||
/**
|
||||
* 标识设备需要执行的具体操作
|
||||
*/
|
||||
private String method;
|
||||
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ThingModelArgument {
|
||||
|
||||
public static final String DIRECTION_INPUT = "input";
|
||||
public static final String DIRECTION_OUTPUT = "output";
|
||||
|
||||
private String identifier;
|
||||
private String name;
|
||||
/**
|
||||
* 物模型中的属性
|
||||
*/
|
||||
private ThingModelProperty property;
|
||||
/**
|
||||
* 用于区分输入或输出参数,"input" 或 "output"
|
||||
*/
|
||||
private String direction;
|
||||
private String description;
|
||||
|
||||
}
|
@ -20,7 +20,7 @@ public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
|
||||
* 数据长度,单位为字节。取值不能超过 2048。
|
||||
* 当 dataType 为 text 时,需传入该参数。
|
||||
*/
|
||||
private Long length;
|
||||
private Integer length;
|
||||
/**
|
||||
* 默认值,可选参数,用于存储默认值。
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -25,20 +25,14 @@ public class ThingModelStructDataSpecs extends ThingModelDataSpecs {
|
||||
* 属性名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 属性描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 云端可以对该属性进行的操作类型
|
||||
* 关联枚举 {@link IotProductThingModelAccessModeEnum}
|
||||
*
|
||||
* 枚举 {@link IotThingModelAccessModeEnum}
|
||||
*/
|
||||
private String accessMode;
|
||||
/**
|
||||
* 是否是标准品类的必选服务。
|
||||
*
|
||||
* - true:是
|
||||
* - false:否
|
||||
* 是否是标准品类的必选服务
|
||||
*/
|
||||
private Boolean required;
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
@ -13,7 +13,7 @@ import lombok.ToString;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IotProductThingModelPageReqVO extends PageParam {
|
||||
public class IotThingModelPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "功能标识")
|
||||
private String identifier;
|
||||
@ -22,7 +22,7 @@ public class IotProductThingModelPageReqVO extends PageParam {
|
||||
private String name;
|
||||
|
||||
@Schema(description = "功能类型", example = "1")
|
||||
@InEnum(IotProductThingModelTypeEnum.class)
|
||||
@InEnum(IotThingModelTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
||||
@Schema(description = "管理后台 - IoT 产品物模型 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class IotProductThingModelRespVO {
|
||||
public class IotThingModelRespVO {
|
||||
|
||||
@Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
|
||||
@ExcelProperty("产品ID")
|
@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
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 cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@ -12,7 +12,7 @@ import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品物模型新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotProductThingModelSaveReqVO {
|
||||
public class IotThingModelSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", example = "1")
|
||||
private Long id;
|
||||
@ -38,7 +38,7 @@ public class IotProductThingModelSaveReqVO {
|
||||
|
||||
@Schema(description = "功能类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "功能类型不能为空")
|
||||
@InEnum(IotProductThingModelTypeEnum.class)
|
||||
@InEnum(IotThingModelTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "属性", requiredMode = Schema.RequiredMode.REQUIRED)
|
@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.convert.thingmodel;
|
||||
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 cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
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.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
@ -16,44 +16,44 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Mapper
|
||||
public interface IotProductThingModelConvert {
|
||||
public interface IotThingModelConvert {
|
||||
|
||||
IotProductThingModelConvert INSTANCE = Mappers.getMapper(IotProductThingModelConvert.class);
|
||||
IotThingModelConvert INSTANCE = Mappers.getMapper(IotThingModelConvert.class);
|
||||
|
||||
// 将 SaveReqVO 转换为 DO
|
||||
@Mapping(target = "property", expression = "java(convertToProperty(bean))")
|
||||
@Mapping(target = "event", expression = "java(convertToEvent(bean))")
|
||||
@Mapping(target = "service", expression = "java(convertToService(bean))")
|
||||
IotProductThingModelDO convert(IotProductThingModelSaveReqVO bean);
|
||||
IotThingModelDO convert(IotThingModelSaveReqVO bean);
|
||||
|
||||
// 将 DO 转换为 RespVO
|
||||
@Mapping(target = "property", source = "property")
|
||||
@Mapping(target = "event", source = "event")
|
||||
@Mapping(target = "service", source = "service")
|
||||
IotProductThingModelRespVO convert(IotProductThingModelDO bean);
|
||||
IotThingModelRespVO convert(IotThingModelDO bean);
|
||||
|
||||
// 批量转换
|
||||
List<IotProductThingModelRespVO> convertList(List<IotProductThingModelDO> list);
|
||||
List<IotThingModelRespVO> convertList(List<IotThingModelDO> list);
|
||||
|
||||
@Named("convertToProperty")
|
||||
default ThingModelProperty convertToProperty(IotProductThingModelSaveReqVO bean) {
|
||||
if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
|
||||
default ThingModelProperty convertToProperty(IotThingModelSaveReqVO bean) {
|
||||
if (Objects.equals(bean.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
|
||||
return bean.getProperty();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Named("convertToEvent")
|
||||
default ThingModelEvent convertToEvent(IotProductThingModelSaveReqVO bean) {
|
||||
if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.EVENT.getType())) {
|
||||
default ThingModelEvent convertToEvent(IotThingModelSaveReqVO bean) {
|
||||
if (Objects.equals(bean.getType(), IotThingModelTypeEnum.EVENT.getType())) {
|
||||
return bean.getEvent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Named("convertToService")
|
||||
default ThingModelService convertToService(IotProductThingModelSaveReqVO bean) {
|
||||
if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.SERVICE.getType())) {
|
||||
default ThingModelService convertToService(IotThingModelSaveReqVO bean) {
|
||||
if (Objects.equals(bean.getType(), IotThingModelTypeEnum.SERVICE.getType())) {
|
||||
return bean.getService();
|
||||
}
|
||||
return null;
|
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -30,7 +30,7 @@ public class IotDeviceDataDO {
|
||||
/**
|
||||
* 物模型编号
|
||||
* <p>
|
||||
* 关联 {@link IotProductThingModelDO#getId()}
|
||||
* 关联 {@link IotThingModelDO#getId()}
|
||||
*/
|
||||
private Long thingModelId;
|
||||
|
||||
@ -51,21 +51,21 @@ public class IotDeviceDataDO {
|
||||
/**
|
||||
* 属性标识符
|
||||
* <p>
|
||||
* 关联 {@link IotProductThingModelDO#getIdentifier()}
|
||||
* 关联 {@link IotThingModelDO#getIdentifier()}
|
||||
*/
|
||||
private String identifier;
|
||||
|
||||
/**
|
||||
* 属性名称
|
||||
* <p>
|
||||
* 关联 {@link IotProductThingModelDO#getName()}
|
||||
* 关联 {@link IotThingModelDO#getName()}
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 数据类型
|
||||
* <p>
|
||||
* 关联 {@link IotProductThingModelDO#getProperty()#getDataType()}
|
||||
* 关联 {@link IotThingModelDO#getProperty()#getDataType()}
|
||||
*/
|
||||
private String dataType;
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
|
||||
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;
|
||||
@ -26,11 +29,10 @@ public class PluginInfoDO extends BaseDO {
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
// TODO @haohao:这个是不是改成类似 key 之类的字段哈?
|
||||
/**
|
||||
* 插件包 ID
|
||||
* 插件包标识符
|
||||
*/
|
||||
private String pluginId;
|
||||
private String pluginKey;
|
||||
/**
|
||||
* 插件名称
|
||||
*/
|
||||
@ -41,22 +43,23 @@ public class PluginInfoDO extends BaseDO {
|
||||
private String description;
|
||||
/**
|
||||
* 部署方式
|
||||
* <p>
|
||||
* 枚举 {@link IotPluginDeployTypeEnum}
|
||||
*/
|
||||
// TODO @haohao:枚举
|
||||
private Integer deployType;
|
||||
/**
|
||||
* 插件包文件名
|
||||
*/
|
||||
// TODO @haohao:是不是叫 fileName 哈?避免后续有别的字段,类似 fileUrl?
|
||||
private String file;
|
||||
private String fileName;
|
||||
/**
|
||||
* 插件版本
|
||||
*/
|
||||
private String version;
|
||||
/**
|
||||
* 插件类型
|
||||
* <p>
|
||||
* 枚举 {@link IotPluginTypeEnum}
|
||||
*/
|
||||
// TODO @haohao:枚举
|
||||
private Integer type;
|
||||
/**
|
||||
* 设备插件协议类型
|
||||
@ -64,8 +67,9 @@ public class PluginInfoDO extends BaseDO {
|
||||
private String protocol;
|
||||
/**
|
||||
* 状态
|
||||
* <p>
|
||||
* 枚举 {@link IotPluginStatusEnum}
|
||||
*/
|
||||
// TODO @haohao:枚举
|
||||
private Integer status;
|
||||
/**
|
||||
* 插件配置项描述信息
|
||||
|
@ -1,12 +1,13 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
// TODO @haohao:一些必要的关联、枚举
|
||||
|
||||
/**
|
||||
* IoT 插件实例 DO
|
||||
*
|
||||
@ -33,6 +34,8 @@ public class PluginInstanceDO extends BaseDO {
|
||||
private String mainId;
|
||||
/**
|
||||
* 插件id
|
||||
* <p>
|
||||
* 关联 {@link PluginInfoDO#getId()}
|
||||
*/
|
||||
private Long pluginId;
|
||||
/**
|
||||
|
@ -5,7 +5,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelR
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* FieldParser 类用于解析和转换物模型字段到 TDengine 字段
|
||||
@ -15,6 +16,7 @@ public class FieldParser {
|
||||
/**
|
||||
* 物模型到td数据类型映射
|
||||
*/
|
||||
@Deprecated
|
||||
private static final HashMap<String, String> TYPE_MAPPING = new HashMap<>() {
|
||||
{
|
||||
put("INT", "INT");
|
||||
@ -52,10 +54,7 @@ public class FieldParser {
|
||||
* @return 字段列表
|
||||
*/
|
||||
public static List<TdFieldDO> parse(ThingModelRespVO thingModel) {
|
||||
// TODO @puhui999:是不是使用 convertList
|
||||
return thingModel.getModel().getProperties().stream()
|
||||
.map(FieldParser::parse)
|
||||
.collect(Collectors.toList());
|
||||
return convertList(thingModel.getModel().getProperties(), FieldParser::parse);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,18 +64,18 @@ public class FieldParser {
|
||||
* @return 转换后的 TDengine 字段对象列表
|
||||
*/
|
||||
public static List<TdFieldDO> parse(List<List<Object>> rows) {
|
||||
// TODO @puhui999:是不是使用 convertList
|
||||
return rows.stream().map(row -> {
|
||||
return convertList(rows, row -> {
|
||||
String type = row.get(1).toString().toUpperCase();
|
||||
// TODO @puhui999:"NCHAR" 最好枚举下
|
||||
int dataLength = "NCHAR".equals(type) ? Integer.parseInt(row.get(2).toString()) : -1;
|
||||
return new TdFieldDO(row.get(0).toString(), type, dataLength);
|
||||
}).collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段字义
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getFieldDefine(TdFieldDO field) {
|
||||
return "`" + field.getFieldName() + "`" + " "
|
||||
+ (field.getDataLength() > 0 ? String.format("%s(%d)", field.getDataType(), field.getDataLength())
|
||||
|
@ -2,13 +2,12 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
// TODO @haohao:类似这个,其实可以参考 mybatis plus,querywrapper,搞个 TdEngineQueryWrapper。这样看起来会更好懂。
|
||||
/**
|
||||
* 查询DO
|
||||
*/
|
||||
@Data
|
||||
@Deprecated
|
||||
public class SelectDO {
|
||||
|
||||
// TODO @haoha:database 是个单词
|
||||
|
@ -6,6 +6,7 @@ import java.util.Map;
|
||||
|
||||
// TODO @haohao:类似 SelectDO 的想法,只是它是返回。ps:貌似可以在 tdengine 里面,创建一个 query 包,放这种比较特殊的查询和结果对象。dataobject 更多还是实际存储的结构化的 do
|
||||
@Data
|
||||
@Deprecated
|
||||
public class SelectVisualDO {
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@ import lombok.Data;
|
||||
* tags查询DO
|
||||
*/
|
||||
@Data
|
||||
@Deprecated
|
||||
public class TagsSelectDO {
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
// TODO 芋艿:看看是不是后续简化掉。
|
||||
/**
|
||||
* TD 引擎的字段
|
||||
*/
|
||||
@ -12,6 +13,7 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Deprecated
|
||||
public class TdFieldDO {
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@ import java.util.List;
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Deprecated
|
||||
public class TdResponse {
|
||||
|
||||
public static final int CODE_SUCCESS = 0;
|
||||
|
@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@Deprecated // TODO 芋艿:貌似没用到
|
||||
public class TdRestApi {
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
|
||||
|
@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated
|
||||
/**
|
||||
* TD 引擎的数据库
|
||||
*/
|
||||
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelE
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@ -19,17 +19,17 @@ import lombok.NoArgsConstructor;
|
||||
/**
|
||||
* IoT 产品物模型功能 DO
|
||||
* <p>
|
||||
* 每个 {@link IotProductDO} 和 {@link IotProductThingModelDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录
|
||||
* 每个 {@link IotProductDO} 和 {@link IotThingModelDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "iot_product_thing_model", autoResultMap = true)
|
||||
@KeySequence("iot_product_thing_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@TableName(value = "iot_thing_model", autoResultMap = true)
|
||||
@KeySequence("iot_thing_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotProductThingModelDO extends BaseDO {
|
||||
public class IotThingModelDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 物模型功能编号
|
||||
@ -66,7 +66,7 @@ public class IotProductThingModelDO extends BaseDO {
|
||||
/**
|
||||
* 功能类型
|
||||
* <p>
|
||||
* 枚举 {@link IotProductThingModelTypeEnum}
|
||||
* 枚举 {@link IotThingModelTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
@ -6,6 +6,7 @@ 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 java.util.List;
|
||||
@ -47,7 +48,8 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
|
||||
}
|
||||
|
||||
default IotDeviceDO selectByDeviceKey(String deviceKey) {
|
||||
return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
|
||||
return selectOne(new LambdaQueryWrapper<IotDeviceDO>()
|
||||
.apply("LOWER(device_key) = {0}", deviceKey.toLowerCase()));
|
||||
}
|
||||
|
||||
default List<IotDeviceDO> selectList(Integer deviceType) {
|
||||
|
@ -0,0 +1,25 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*;
|
||||
|
||||
/**
|
||||
* IoT 插件信息 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface PluginInfoMapper extends BaseMapperX<PluginInfoDO> {
|
||||
|
||||
default PageResult<PluginInfoDO> selectPage(PluginInfoPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<PluginInfoDO>()
|
||||
.likeIfPresent(PluginInfoDO::getName, reqVO.getName())
|
||||
.eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(PluginInfoDO::getId));
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.plugininstance;
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstancePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
|
||||
|
||||
/**
|
||||
* IoT 插件实例 Mapper
|
||||
@ -15,6 +15,12 @@ import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
|
||||
@Mapper
|
||||
public interface PluginInstanceMapper extends BaseMapperX<PluginInstanceDO> {
|
||||
|
||||
default PluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) {
|
||||
return selectOne(new LambdaQueryWrapperX<PluginInstanceDO>()
|
||||
.eq(PluginInstanceDO::getMainId, mainId)
|
||||
.eq(PluginInstanceDO::getPluginId, pluginId));
|
||||
}
|
||||
|
||||
default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
|
||||
.eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())
|
@ -1,36 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.plugininfo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*;
|
||||
|
||||
/**
|
||||
* IoT 插件信息 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface PluginInfoMapper extends BaseMapperX<PluginInfoDO> {
|
||||
|
||||
default PageResult<PluginInfoDO> selectPage(PluginInfoPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<PluginInfoDO>()
|
||||
.eqIfPresent(PluginInfoDO::getPluginId, reqVO.getPluginId())
|
||||
.likeIfPresent(PluginInfoDO::getName, reqVO.getName())
|
||||
.eqIfPresent(PluginInfoDO::getDescription, reqVO.getDescription())
|
||||
.eqIfPresent(PluginInfoDO::getDeployType, reqVO.getDeployType())
|
||||
.eqIfPresent(PluginInfoDO::getFile, reqVO.getFile())
|
||||
.eqIfPresent(PluginInfoDO::getVersion, reqVO.getVersion())
|
||||
.eqIfPresent(PluginInfoDO::getType, reqVO.getType())
|
||||
.eqIfPresent(PluginInfoDO::getProtocol, reqVO.getProtocol())
|
||||
.eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(PluginInfoDO::getConfigSchema, reqVO.getConfigSchema())
|
||||
.eqIfPresent(PluginInfoDO::getConfig, reqVO.getConfig())
|
||||
.eqIfPresent(PluginInfoDO::getScript, reqVO.getScript())
|
||||
.betweenIfPresent(PluginInfoDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(PluginInfoDO::getId));
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ 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.product.vo.product.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
@ -23,7 +24,8 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
|
||||
}
|
||||
|
||||
default IotProductDO selectByProductKey(String productKey) {
|
||||
return selectOne(IotProductDO::getProductKey, productKey);
|
||||
return selectOne(new LambdaQueryWrapper<IotProductDO>()
|
||||
.apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.thingmodel;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
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.thingmodel.vo.IotProductThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 产品物模型 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotProductThingModelMapper extends BaseMapperX<IotProductThingModelDO> {
|
||||
|
||||
default PageResult<IotProductThingModelDO> selectPage(IotProductThingModelPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotProductThingModelDO>()
|
||||
.eqIfPresent(IotProductThingModelDO::getIdentifier, reqVO.getIdentifier())
|
||||
.likeIfPresent(IotProductThingModelDO::getName, reqVO.getName())
|
||||
.eqIfPresent(IotProductThingModelDO::getType, reqVO.getType())
|
||||
.eqIfPresent(IotProductThingModelDO::getProductId, reqVO.getProductId())
|
||||
.notIn(IotProductThingModelDO::getIdentifier, "get", "set", "post")
|
||||
.orderByDesc(IotProductThingModelDO::getId));
|
||||
}
|
||||
|
||||
default IotProductThingModelDO selectByProductIdAndIdentifier(Long productId, String identifier) {
|
||||
return selectOne(IotProductThingModelDO::getProductId, productId,
|
||||
IotProductThingModelDO::getIdentifier, identifier);
|
||||
}
|
||||
|
||||
default List<IotProductThingModelDO> selectListByProductId(Long productId) {
|
||||
return selectList(IotProductThingModelDO::getProductId, productId);
|
||||
}
|
||||
|
||||
default List<IotProductThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
|
||||
return selectList(IotProductThingModelDO::getProductId, productId,
|
||||
IotProductThingModelDO::getType, type);
|
||||
}
|
||||
|
||||
default List<IotProductThingModelDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
|
||||
List<String> identifiers,
|
||||
List<Integer> types) {
|
||||
return selectList(new LambdaQueryWrapperX<IotProductThingModelDO>()
|
||||
.eq(IotProductThingModelDO::getProductId, productId)
|
||||
.in(IotProductThingModelDO::getIdentifier, identifiers)
|
||||
.in(IotProductThingModelDO::getType, types));
|
||||
}
|
||||
|
||||
default IotProductThingModelDO selectByProductIdAndName(Long productId, String name) {
|
||||
return selectOne(IotProductThingModelDO::getProductId, productId,
|
||||
IotProductThingModelDO::getName, name);
|
||||
}
|
||||
|
||||
default List<IotProductThingModelDO> selectListByProductKey(String productKey) {
|
||||
return selectList(IotProductThingModelDO::getProductKey, productKey);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.thingmodel;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
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.thingmodel.vo.IotThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 产品物模型 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotThingModelMapper extends BaseMapperX<IotThingModelDO> {
|
||||
|
||||
default PageResult<IotThingModelDO> selectPage(IotThingModelPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotThingModelDO>()
|
||||
.eqIfPresent(IotThingModelDO::getIdentifier, reqVO.getIdentifier())
|
||||
.likeIfPresent(IotThingModelDO::getName, reqVO.getName())
|
||||
.eqIfPresent(IotThingModelDO::getType, reqVO.getType())
|
||||
.eqIfPresent(IotThingModelDO::getProductId, reqVO.getProductId())
|
||||
// TODO @芋艿:看看要不要加枚举
|
||||
.notIn(IotThingModelDO::getIdentifier, "get", "set", "post")
|
||||
.orderByDesc(IotThingModelDO::getId));
|
||||
}
|
||||
|
||||
default IotThingModelDO selectByProductIdAndIdentifier(Long productId, String identifier) {
|
||||
return selectOne(IotThingModelDO::getProductId, productId,
|
||||
IotThingModelDO::getIdentifier, identifier);
|
||||
}
|
||||
|
||||
default List<IotThingModelDO> selectListByProductId(Long productId) {
|
||||
return selectList(IotThingModelDO::getProductId, productId);
|
||||
}
|
||||
|
||||
default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
|
||||
return selectList(IotThingModelDO::getProductId, productId,
|
||||
IotThingModelDO::getType, type);
|
||||
}
|
||||
|
||||
default List<IotThingModelDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
|
||||
List<String> identifiers,
|
||||
List<Integer> types) {
|
||||
return selectList(new LambdaQueryWrapperX<IotThingModelDO>()
|
||||
.eq(IotThingModelDO::getProductId, productId)
|
||||
.in(IotThingModelDO::getIdentifier, identifiers)
|
||||
.in(IotThingModelDO::getType, types));
|
||||
}
|
||||
|
||||
default IotThingModelDO selectByProductIdAndName(Long productId, String name) {
|
||||
return selectOne(IotThingModelDO::getProductId, productId,
|
||||
IotThingModelDO::getName, name);
|
||||
}
|
||||
|
||||
default List<IotThingModelDO> selectListByProductKey(String productKey) {
|
||||
return selectList(IotThingModelDO::getProductKey, productKey);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.tdengine;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
|
||||
import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper
|
||||
@TDengineDS
|
||||
@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
|
||||
public interface IotDevicePropertyDataMapper {
|
||||
|
||||
List<TDengineTableField> getProductPropertySTableFieldList(@Param("productKey") String productKey);
|
||||
|
||||
void createProductPropertySTable(@Param("productKey") String productKey,
|
||||
@Param("fields") List<TDengineTableField> fields);
|
||||
|
||||
@SuppressWarnings("SimplifyStreamApiCallChains") // 保持 JDK8 兼容性
|
||||
default void alterProductPropertySTable(String productKey,
|
||||
List<TDengineTableField> oldFields,
|
||||
List<TDengineTableField> newFields) {
|
||||
oldFields.removeIf(field -> TDengineTableField.FIELD_TS.equals(field.getField())
|
||||
|| TDengineTableField.FIELD_DEVICE_KEY.equals(field.getField()));
|
||||
List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段
|
||||
newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField())))
|
||||
.collect(Collectors.toList());
|
||||
List<TDengineTableField> dropFields = oldFields.stream().filter( // 删除的字段
|
||||
oldField -> newFields.stream().noneMatch(n -> n.getField().equals(oldField.getField())))
|
||||
.collect(Collectors.toList());
|
||||
List<TDengineTableField> modifyTypeFields = new ArrayList<>(); // 变更类型的字段
|
||||
List<TDengineTableField> modifyLengthFields = new ArrayList<>(); // 变更长度的字段
|
||||
newFields.forEach(newField -> {
|
||||
TDengineTableField oldField = CollUtil.findOne(oldFields, field -> field.getField().equals(newField.getField()));
|
||||
if (oldField == null) {
|
||||
return;
|
||||
}
|
||||
if (ObjectUtil.notEqual(oldField.getType(), newField.getType())) {
|
||||
modifyTypeFields.add(newField);
|
||||
return;
|
||||
}
|
||||
if (newField.getLength()!= null) {
|
||||
if (newField.getLength() > oldField.getLength()) {
|
||||
modifyLengthFields.add(newField);
|
||||
} else if (newField.getLength() < oldField.getLength()) {
|
||||
// 特殊:TDengine 长度修改时,只允许变长,所以此时认为是修改类型
|
||||
modifyTypeFields.add(newField);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 执行
|
||||
addFields.forEach(field -> alterProductPropertySTableAddField(productKey, field));
|
||||
dropFields.forEach(field -> alterProductPropertySTableDropField(productKey, field));
|
||||
modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productKey, field));
|
||||
modifyTypeFields.forEach(field -> {
|
||||
alterProductPropertySTableDropField(productKey, field);
|
||||
alterProductPropertySTableAddField(productKey, field);
|
||||
});
|
||||
}
|
||||
|
||||
void alterProductPropertySTableAddField(@Param("productKey") String productKey,
|
||||
@Param("field") TDengineTableField field);
|
||||
|
||||
void alterProductPropertySTableModifyField(@Param("productKey") String productKey,
|
||||
@Param("field") TDengineTableField field);
|
||||
|
||||
void alterProductPropertySTableDropField(@Param("productKey") String productKey,
|
||||
@Param("field") TDengineTableField field);
|
||||
|
||||
}
|
@ -96,7 +96,6 @@ public interface TdEngineDDLMapper {
|
||||
@TenantIgnore
|
||||
void modifyColumnWidthForSuperTable(TdTableDO superTable);
|
||||
|
||||
|
||||
/**
|
||||
* 修改超级表 - 为超级表添加标签
|
||||
*
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.emq.service;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
@ -21,7 +20,7 @@ import org.springframework.stereotype.Service;
|
||||
public class EmqxServiceImpl implements EmqxService {
|
||||
|
||||
@Resource
|
||||
private IotDeviceDataService iotDeviceDataService;
|
||||
private IotDevicePropertyDataService iotDeviceDataService;
|
||||
|
||||
// TODO 多线程处理消息
|
||||
@Override
|
||||
|
@ -1,34 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.plugin;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ServiceRegistryConfiguration {
|
||||
|
||||
@Resource
|
||||
private DeviceDataApi deviceDataApi;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 将主程序中的 DeviceDataApi 实例注册到 ServiceRegistry
|
||||
ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
|
||||
log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成
|
||||
*/
|
||||
@Bean("serviceRegistryInitializedMarker")
|
||||
public Object serviceRegistryInitializedMarker() {
|
||||
// 返回任意对象即可,这里返回null都可以,但最好返回个实际对象
|
||||
return new Object();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.plugin;
|
||||
|
||||
import org.pf4j.spring.SpringPluginManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
@Configuration
|
||||
public class SpringConfiguration {
|
||||
|
||||
@Bean
|
||||
@DependsOn("serviceRegistryInitializedMarker")
|
||||
public SpringPluginManager pluginManager() {
|
||||
return new SpringPluginManager();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.plugin;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
|
||||
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.framework.plugin.listener.CustomPluginStateListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.spring.SpringPluginManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class UnifiedConfiguration {
|
||||
|
||||
private static final String SERVICE_REGISTRY_INITIALIZED_MARKER = "serviceRegistryInitializedMarker";
|
||||
|
||||
@Resource
|
||||
private DeviceDataApi deviceDataApi;
|
||||
|
||||
@Bean(SERVICE_REGISTRY_INITIALIZED_MARKER)
|
||||
public Object serviceRegistryInitializedMarker() {
|
||||
ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
|
||||
log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
|
||||
return new Object();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
|
||||
public SpringPluginManager pluginManager() {
|
||||
log.info("[init][实例化 SpringPluginManager]");
|
||||
SpringPluginManager springPluginManager = new SpringPluginManager();
|
||||
springPluginManager.addPluginStateListener(new CustomPluginStateListener());
|
||||
return springPluginManager;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.plugin.listener;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginStateEvent;
|
||||
import org.pf4j.PluginStateListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CustomPluginStateListener implements PluginStateListener {
|
||||
|
||||
@Override
|
||||
public void pluginStateChanged(PluginStateEvent event) {
|
||||
// 1. 获取插件ID
|
||||
String pluginId = event.getPlugin().getPluginId();
|
||||
// 2. 获取插件旧状态
|
||||
String oldState = event.getOldState().toString();
|
||||
// 3. 获取插件新状态
|
||||
String newState = event.getPluginState().toString();
|
||||
// 4. 打印日志信息
|
||||
log.info("插件的状态 '{}' 已更改为 '{}' 至 '{}'", pluginId, oldState, newState);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.tdengine.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* TDEngine 表字段
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TDengineTableField {
|
||||
|
||||
/**
|
||||
* 字段名 - TDengine 默认 ts 字段,默认会被 TDengine 创建
|
||||
*/
|
||||
public static final String FIELD_TS = "ts";
|
||||
|
||||
/**
|
||||
* 字段名 - 我们系统定义的 device_key 字段,非 TDengine 默认字段
|
||||
*/
|
||||
public static final String FIELD_DEVICE_KEY = "device_key";
|
||||
|
||||
public static final String TYPE_TINYINT = "TINYINT";
|
||||
public static final String TYPE_INT = "INT";
|
||||
public static final String TYPE_FLOAT = "FLOAT";
|
||||
public static final String TYPE_DOUBLE = "DOUBLE";
|
||||
public static final String TYPE_BOOL = "BOOL";
|
||||
public static final String TYPE_NCHAR = "NCHAR";
|
||||
public static final String TYPE_TIMESTAMP = "TIMESTAMP";
|
||||
|
||||
/**
|
||||
* 注释 - TAG 字段
|
||||
*/
|
||||
public static final String NOTE_TAG = "TAG";
|
||||
|
||||
/**
|
||||
* 字段名
|
||||
*/
|
||||
private String field;
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 字段长度
|
||||
*/
|
||||
private Integer length;
|
||||
|
||||
/**
|
||||
* 注释
|
||||
*/
|
||||
private String note;
|
||||
|
||||
public TDengineTableField(String field, String type) {
|
||||
this.field = field;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation;
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* TDEngine 数据源
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@DS("tdengine")
|
||||
public @interface TDengineDS {
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* iot 模块的 tdengine 拓展封装
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.framework.tdengine;
|
@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.iot.job.plugin;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 插件实例 Job
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class PluginInstancesJob {
|
||||
|
||||
@Resource
|
||||
private PluginInstanceService pluginInstanceService;
|
||||
|
||||
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
|
||||
public void updatePluginInstances() {
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
pluginInstanceService.updatePluginInstances();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package cn.iocoder.yudao.module.iot.mq.consumer.simulatesend;
|
||||
|
||||
/**
|
||||
* TODO @alwayssuper:记得实现,还有类注释哈
|
||||
*
|
||||
* @author alwayssuper
|
||||
* @date 2024/12/20 8:04
|
||||
* @since 2024/12/20 8:04
|
||||
*/
|
||||
public class SimulateSendConsumer {
|
||||
}
|
||||
|
@ -9,11 +9,18 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 设备数据 Service 接口
|
||||
* IoT 设备【属性】数据 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotDeviceDataService {
|
||||
public interface IotDevicePropertyDataService {
|
||||
|
||||
/**
|
||||
* 定义设备属性数据的结构
|
||||
*
|
||||
* @param productId 产品编号
|
||||
*/
|
||||
void defineDevicePropertyData(Long productId);
|
||||
|
||||
/**
|
||||
* 保存设备数据
|
||||
@ -21,7 +28,7 @@ public interface IotDeviceDataService {
|
||||
* @param productKey 产品 key
|
||||
* @param deviceName 设备名称
|
||||
* @param message 消息
|
||||
* <p>JSON 格式,参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">...</a>
|
||||
* <p>参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">JSON 格式</a>
|
||||
*/
|
||||
void saveDeviceData(String productKey, String deviceName, String message);
|
||||
|
||||
@ -40,4 +47,5 @@ public interface IotDeviceDataService {
|
||||
* @return 设备属性历史数据
|
||||
*/
|
||||
PageResult<Map<String, Object>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO);
|
||||
|
||||
}
|
@ -1,21 +1,29 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.IotConstants;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -28,11 +36,32 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
|
||||
@Slf4j
|
||||
/**
|
||||
* IoT 设备【属性】数据 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
public class IotDeviceDataServiceImpl implements IotDeviceDataService {
|
||||
@Slf4j
|
||||
public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataService {
|
||||
|
||||
/**
|
||||
* 物模型的数据类型,与 TDengine 数据类型的映射关系
|
||||
*/
|
||||
private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
|
||||
.put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
|
||||
.put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
|
||||
.put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
|
||||
.put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
|
||||
.put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
|
||||
.put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR)
|
||||
.put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
|
||||
.put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
|
||||
.put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
|
||||
.build();
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
|
||||
private String url;
|
||||
@ -42,13 +71,62 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
|
||||
@Resource
|
||||
private IotThingModelMessageService thingModelMessageService;
|
||||
@Resource
|
||||
private IotProductThingModelService thingModelService;
|
||||
private IotThingModelService thingModelService;
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
|
||||
@Resource
|
||||
private TdEngineDMLMapper tdEngineDMLMapper;
|
||||
|
||||
@Resource
|
||||
private DeviceDataRedisDAO deviceDataRedisDAO;
|
||||
|
||||
@Resource
|
||||
private IotDevicePropertyDataMapper devicePropertyDataMapper;
|
||||
|
||||
@Override
|
||||
public void defineDevicePropertyData(Long productId) {
|
||||
// 1.1 查询产品和物模型
|
||||
IotProductDO product = productService.validateProductExists(productId);
|
||||
List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId),
|
||||
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
|
||||
// 1.2 解析 DB 里的字段
|
||||
List<TDengineTableField> oldFields = new ArrayList<>();
|
||||
try {
|
||||
oldFields.addAll(devicePropertyDataMapper.getProductPropertySTableFieldList(product.getProductKey()));
|
||||
} catch (Exception e) {
|
||||
if (!e.getMessage().contains("Table does not exist")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 2.1 情况一:如果是新增的时候,需要创建表
|
||||
List<TDengineTableField> newFields = buildTableFieldList(thingModels);
|
||||
if (CollUtil.isEmpty(oldFields)) {
|
||||
if (CollUtil.isEmpty(newFields)) {
|
||||
log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
|
||||
return;
|
||||
}
|
||||
newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP));
|
||||
devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields);
|
||||
return;
|
||||
}
|
||||
// 2.2 情况二:如果是修改的时候,需要更新表
|
||||
devicePropertyDataMapper.alterProductPropertySTable(product.getProductKey(), oldFields, newFields);
|
||||
}
|
||||
|
||||
private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
|
||||
return convertList(thingModels, thingModel -> {
|
||||
TDengineTableField field = new TDengineTableField(
|
||||
StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
|
||||
TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
|
||||
if (thingModel.getProperty().getDataType().equals(IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
|
||||
field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
|
||||
}
|
||||
return field;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDeviceData(String productKey, String deviceName, String message) {
|
||||
// 1. 根据产品 key 和设备名称,获得设备信息
|
||||
@ -75,8 +153,8 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
|
||||
// 1. 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
|
||||
// 2. 获取设备属性最新数据
|
||||
List<IotProductThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
|
||||
thingModelList = filterList(thingModelList, thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType()
|
||||
List<IotThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
|
||||
thingModelList = filterList(thingModelList, thingModel -> IotThingModelTypeEnum.PROPERTY.getType()
|
||||
.equals(thingModel.getType()));
|
||||
|
||||
// 3. 过滤标识符和属性名称
|
@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
|
||||
@ -60,9 +61,11 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
throw exception(PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
// 1.2 校验设备标识是否唯一
|
||||
if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
|
||||
throw exception(DEVICE_KEY_EXISTS);
|
||||
}
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
|
||||
throw exception(PRODUCT_KEY_EXISTS);
|
||||
}
|
||||
});
|
||||
// 1.3 校验设备名称在同一产品下是否唯一
|
||||
if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
|
||||
throw exception(DEVICE_NAME_EXISTS);
|
||||
|
@ -1,12 +1,14 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugininfo;
|
||||
package cn.iocoder.yudao.module.iot.service.plugin;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 插件信息 Service 接口
|
||||
*
|
||||
@ -58,7 +60,7 @@ public interface PluginInfoService {
|
||||
* @param id 插件id
|
||||
* @param file 文件
|
||||
*/
|
||||
void uploadJar(Long id, MultipartFile file);
|
||||
void uploadFile(Long id, MultipartFile file);
|
||||
|
||||
/**
|
||||
* 更新插件的状态
|
||||
@ -67,4 +69,11 @@ public interface PluginInfoService {
|
||||
* @param status 状态
|
||||
*/
|
||||
void updatePluginStatus(Long id, Integer status);
|
||||
|
||||
/**
|
||||
* 获得插件信息列表
|
||||
*
|
||||
* @return 插件信息列表
|
||||
*/
|
||||
List<PluginInfoDO> getPluginInfoList();
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugin;
|
||||
|
||||
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.PluginInfoPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.spring.SpringPluginManager;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* IoT 插件信息 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PluginInfoServiceImpl implements PluginInfoService {
|
||||
|
||||
@Resource
|
||||
private PluginInfoMapper pluginInfoMapper;
|
||||
@Resource
|
||||
private SpringPluginManager pluginManager;
|
||||
|
||||
@Value("${pf4j.pluginsDir}")
|
||||
private String pluginsDir;
|
||||
|
||||
@Override
|
||||
public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
|
||||
pluginInfoMapper.insert(pluginInfo);
|
||||
// 返回
|
||||
return pluginInfo.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validatePluginInfoExists(updateReqVO.getId());
|
||||
// 更新
|
||||
PluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, PluginInfoDO.class);
|
||||
pluginInfoMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePluginInfo(Long id) {
|
||||
// 校验存在
|
||||
PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
|
||||
|
||||
// 停止插件
|
||||
if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
|
||||
throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
|
||||
}
|
||||
|
||||
// 卸载插件
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey());
|
||||
if (plugin != null) {
|
||||
// 查询插件是否是启动状态
|
||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||
// 停止插件
|
||||
pluginManager.stopPlugin(plugin.getPluginId());
|
||||
}
|
||||
// 卸载插件
|
||||
pluginManager.unloadPlugin(plugin.getPluginId());
|
||||
}
|
||||
|
||||
// 删除
|
||||
pluginInfoMapper.deleteById(id);
|
||||
// 删除插件文件
|
||||
Executors.newSingleThreadExecutor().submit(() -> {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
|
||||
File file = new File(pluginsDir, pluginInfoDO.getFileName());
|
||||
if (file.exists() && !file.delete()) {
|
||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private PluginInfoDO validatePluginInfoExists(Long id) {
|
||||
PluginInfoDO pluginInfo = pluginInfoMapper.selectById(id);
|
||||
if (pluginInfo == null) {
|
||||
throw exception(PLUGIN_INFO_NOT_EXISTS);
|
||||
}
|
||||
return pluginInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginInfoDO getPluginInfo(Long id) {
|
||||
return pluginInfoMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PluginInfoDO> getPluginInfoPage(PluginInfoPageReqVO pageReqVO) {
|
||||
return pluginInfoMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadFile(Long id, MultipartFile file) {
|
||||
// 1. 校验插件信息是否存在
|
||||
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
||||
|
||||
// 2. 获取插件标识
|
||||
String pluginKey = pluginInfoDo.getPluginKey();
|
||||
|
||||
// 3. 停止并卸载旧的插件
|
||||
stopAndUnloadPlugin(pluginKey);
|
||||
|
||||
// 4. 上传新的插件文件
|
||||
String pluginKeyNew = uploadAndLoadNewPlugin(file);
|
||||
|
||||
// 5. 更新插件启用状态文件
|
||||
updatePluginStatusFile(pluginKeyNew, false);
|
||||
|
||||
// 6. 更新插件信息
|
||||
updatePluginInfo(pluginInfoDo, pluginKeyNew, file);
|
||||
}
|
||||
|
||||
// 停止并卸载旧的插件
|
||||
private void stopAndUnloadPlugin(String pluginKey) {
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
||||
if (plugin != null) {
|
||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||
pluginManager.stopPlugin(pluginKey); // 停止插件
|
||||
}
|
||||
pluginManager.unloadPlugin(pluginKey); // 卸载插件
|
||||
}
|
||||
}
|
||||
|
||||
// 上传并加载新的插件文件
|
||||
private String uploadAndLoadNewPlugin(MultipartFile file) {
|
||||
Path pluginsPath = Paths.get(pluginsDir);
|
||||
try {
|
||||
if (!Files.exists(pluginsPath)) {
|
||||
Files.createDirectories(pluginsPath); // 创建插件目录
|
||||
}
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename != null) {
|
||||
Path jarPath = pluginsPath.resolve(filename);
|
||||
Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
|
||||
return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
|
||||
} else {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新插件状态文件
|
||||
private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) {
|
||||
Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt");
|
||||
Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt");
|
||||
Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath;
|
||||
Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath;
|
||||
|
||||
try {
|
||||
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew);
|
||||
if (pluginWrapper == null) {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
String pluginInfo = pluginKeyNew + "@" + pluginWrapper.getDescriptor().getVersion();
|
||||
List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)
|
||||
: new ArrayList<>();
|
||||
List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)
|
||||
: new ArrayList<>();
|
||||
|
||||
if (!targetLines.contains(pluginInfo)) {
|
||||
targetLines.add(pluginInfo);
|
||||
Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
|
||||
if (oppositeLines.contains(pluginInfo)) {
|
||||
oppositeLines.remove(pluginInfo);
|
||||
Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新插件信息
|
||||
private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) {
|
||||
pluginInfoDo.setPluginKey(pluginKeyNew);
|
||||
pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
|
||||
pluginInfoDo.setFileName(file.getOriginalFilename());
|
||||
pluginInfoDo.setScript("");
|
||||
|
||||
PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor();
|
||||
pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());
|
||||
pluginInfoDo.setVersion(pluginDescriptor.getVersion());
|
||||
pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
|
||||
pluginInfoMapper.updateById(pluginInfoDo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePluginStatus(Long id, Integer status) {
|
||||
// 1. 校验插件信息是否存在
|
||||
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
||||
|
||||
// 2. 校验插件状态是否有效
|
||||
if (!IotPluginStatusEnum.contains(status)) {
|
||||
throw exception(PLUGIN_STATUS_INVALID);
|
||||
}
|
||||
|
||||
// 3. 获取插件标识和插件实例
|
||||
String pluginKey = pluginInfoDo.getPluginKey();
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
||||
|
||||
// 4. 根据状态更新插件
|
||||
if (plugin != null) {
|
||||
// 4.1 如果目标状态是运行且插件未启动,则启动插件
|
||||
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
|
||||
&& plugin.getPluginState() != PluginState.STARTED) {
|
||||
pluginManager.startPlugin(pluginKey);
|
||||
updatePluginStatusFile(pluginKey, true); // 更新插件状态文件为启用
|
||||
}
|
||||
// 4.2 如果目标状态是停止且插件已启动,则停止插件
|
||||
else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
|
||||
&& plugin.getPluginState() == PluginState.STARTED) {
|
||||
pluginManager.stopPlugin(pluginKey);
|
||||
updatePluginStatusFile(pluginKey, false); // 更新插件状态文件为禁用
|
||||
}
|
||||
} else {
|
||||
// 5. 插件不存在且状态为停止,抛出异常
|
||||
if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
|
||||
throw exception(PLUGIN_STATUS_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 更新数据库中的插件状态
|
||||
pluginInfoDo.setStatus(status);
|
||||
pluginInfoMapper.updateById(pluginInfoDo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginInfoDO> getPluginInfoList() {
|
||||
return pluginInfoMapper.selectList(null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugin;
|
||||
|
||||
/**
|
||||
* IoT 插件实例 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PluginInstanceService {
|
||||
|
||||
/**
|
||||
* 更新IoT 插件实例
|
||||
*/
|
||||
void updatePluginInstances();
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugin;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.spring.SpringPluginManager;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 插件实例 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||
|
||||
/**
|
||||
* 主程序id
|
||||
*/
|
||||
public static final String MAIN_ID = IdUtil.fastSimpleUUID();
|
||||
|
||||
@Resource
|
||||
private PluginInfoService pluginInfoService;
|
||||
@Resource
|
||||
private PluginInstanceMapper pluginInstanceMapper;
|
||||
@Resource
|
||||
private SpringPluginManager pluginManager;
|
||||
|
||||
@Value("${server.port:48080}")
|
||||
private int port;
|
||||
|
||||
|
||||
@Override
|
||||
public void updatePluginInstances() {
|
||||
// 1. 查询 pf4j 插件列表
|
||||
List<PluginWrapper> plugins = pluginManager.getPlugins();
|
||||
|
||||
// 2. 查询插件信息列表
|
||||
List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList();
|
||||
|
||||
// 动态获取主程序的 IP 和端口
|
||||
String mainIp = getLocalIpAddress();
|
||||
|
||||
// 3. 遍历插件列表,并保存为插件实例
|
||||
for (PluginWrapper plugin : plugins) {
|
||||
String pluginKey = plugin.getPluginId();
|
||||
PluginInfoDO pluginInfo = pluginInfos.stream()
|
||||
.filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
// 4. 如果插件信息不存在,则跳过
|
||||
if (pluginInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 5. 查询插件实例
|
||||
PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId());
|
||||
|
||||
// 6. 如果插件实例不存在,则创建
|
||||
if (pluginInstance == null) {
|
||||
pluginInstance = new PluginInstanceDO();
|
||||
pluginInstance.setPluginId(pluginInfo.getId());
|
||||
pluginInstance.setMainId(MAIN_ID);
|
||||
pluginInstance.setIp(mainIp);
|
||||
pluginInstance.setPort(port);
|
||||
pluginInstance.setHeartbeatAt(System.currentTimeMillis());
|
||||
pluginInstanceMapper.insert(pluginInstance);
|
||||
} else {
|
||||
// 7. 如果插件实例存在,则更新
|
||||
pluginInstance.setHeartbeatAt(System.currentTimeMillis());
|
||||
pluginInstanceMapper.updateById(pluginInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getLocalIpAddress() {
|
||||
try {
|
||||
List<String> ipList = NetUtil.localIpv4s().stream()
|
||||
.filter(ip -> !ip.startsWith("0.0") && !ip.startsWith("127.") && !ip.startsWith("169.254") && !ip.startsWith("255.255.255.255"))
|
||||
.toList();
|
||||
return ipList.isEmpty() ? "127.0.0.1" : ipList.get(0);
|
||||
} catch (Exception e) {
|
||||
log.error("获取本地IP地址失败", e);
|
||||
return "127.0.0.1"; // 默认值
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugininfo;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.spring.SpringPluginManager;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* IoT 插件信息 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PluginInfoServiceImpl implements PluginInfoService {
|
||||
|
||||
@Resource
|
||||
private PluginInfoMapper pluginInfoMapper;
|
||||
|
||||
@Resource
|
||||
private SpringPluginManager pluginManager;
|
||||
|
||||
@Resource
|
||||
private FileApi fileApi;
|
||||
|
||||
@Value("${pf4j.pluginsDir}")
|
||||
private String pluginsDir;
|
||||
|
||||
@Override
|
||||
public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
|
||||
pluginInfoMapper.insert(pluginInfo);
|
||||
// 返回
|
||||
return pluginInfo.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validatePluginInfoExists(updateReqVO.getId());
|
||||
// 更新
|
||||
PluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, PluginInfoDO.class);
|
||||
pluginInfoMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePluginInfo(Long id) {
|
||||
// 校验存在
|
||||
PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
|
||||
|
||||
// 停止插件
|
||||
if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
|
||||
throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
|
||||
}
|
||||
|
||||
// 卸载插件
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginId());
|
||||
if (plugin != null) {
|
||||
// 查询插件是否是启动状态
|
||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||
// 停止插件
|
||||
pluginManager.stopPlugin(plugin.getPluginId());
|
||||
}
|
||||
// 卸载插件
|
||||
pluginManager.unloadPlugin(plugin.getPluginId());
|
||||
}
|
||||
|
||||
// 删除
|
||||
pluginInfoMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private PluginInfoDO validatePluginInfoExists(Long id) {
|
||||
PluginInfoDO pluginInfo = pluginInfoMapper.selectById(id);
|
||||
if (pluginInfo == null) {
|
||||
throw exception(PLUGIN_INFO_NOT_EXISTS);
|
||||
}
|
||||
return pluginInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginInfoDO getPluginInfo(Long id) {
|
||||
return pluginInfoMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PluginInfoDO> getPluginInfoPage(PluginInfoPageReqVO pageReqVO) {
|
||||
return pluginInfoMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadJar(Long id, MultipartFile file) {
|
||||
// 1. 校验存在
|
||||
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
||||
|
||||
// 2. 判断文件名称与插件 ID 是否匹配
|
||||
String pluginId = pluginInfoDo.getPluginId();
|
||||
|
||||
// 3. 停止卸载旧的插件
|
||||
// 3.1. 获取插件信息
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||
if (plugin != null) {
|
||||
// 3.2. 如果插件状态是启动的,停止插件
|
||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||
pluginManager.stopPlugin(pluginId);
|
||||
}
|
||||
// 3.3. 卸载插件
|
||||
pluginManager.unloadPlugin(pluginId);
|
||||
}
|
||||
|
||||
// 4. 上传插件
|
||||
String pluginIdNew;
|
||||
try {
|
||||
String path = fileApi.createFile(pluginsDir, IoUtil.readBytes(file.getInputStream()));
|
||||
Path pluginPath = Path.of(path);
|
||||
pluginIdNew = pluginManager.loadPlugin(pluginPath);
|
||||
} catch (Exception e) {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
|
||||
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew);
|
||||
if (pluginWrapper == null) {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
|
||||
// 5. 读取配置文件和脚本
|
||||
String configJson = "";
|
||||
String script = "";
|
||||
try (JarFile jarFile = new JarFile(pluginWrapper.getPluginPath().toFile())) {
|
||||
// 5.1 获取config文件在jar包中的路径
|
||||
String configFile = "classes/config.json";
|
||||
JarEntry configEntry = jarFile.getJarEntry(configFile);
|
||||
|
||||
if (configEntry != null) {
|
||||
// 5.2 读取配置文件
|
||||
configJson = IoUtil.readUtf8(jarFile.getInputStream(configEntry));
|
||||
log.info("configJson:{}", configJson);
|
||||
}
|
||||
|
||||
// 5.3 读取script.js脚本
|
||||
String scriptFile = "classes/script.js";
|
||||
JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
|
||||
if (scriptEntity != null) {
|
||||
// 5.4 读取脚本文件
|
||||
script = IoUtil.readUtf8(jarFile.getInputStream(scriptEntity));
|
||||
log.info("script:{}", script);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw exception(PLUGIN_INSTALL_FAILED);
|
||||
}
|
||||
|
||||
pluginInfoDo.setPluginId(pluginIdNew);
|
||||
pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
|
||||
pluginInfoDo.setFile(file.getOriginalFilename());
|
||||
pluginInfoDo.setConfigSchema(configJson);
|
||||
pluginInfoDo.setScript(script);
|
||||
|
||||
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
|
||||
pluginInfoDo.setVersion(pluginDescriptor.getVersion());
|
||||
pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
|
||||
pluginInfoMapper.updateById(pluginInfoDo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePluginStatus(Long id, Integer status) {
|
||||
// 1. 校验存在
|
||||
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
||||
|
||||
// 插件状态无效
|
||||
if (!IotPluginStatusEnum.contains(status)) {
|
||||
throw exception(PLUGIN_STATUS_INVALID);
|
||||
}
|
||||
|
||||
String pluginId = pluginInfoDo.getPluginId();
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||
if (plugin != null) {
|
||||
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) {
|
||||
// 启动插件
|
||||
pluginManager.startPlugin(pluginId);
|
||||
} else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) {
|
||||
// 停止插件
|
||||
pluginManager.stopPlugin(pluginId);
|
||||
}
|
||||
} else {
|
||||
// 已经停止,未获取到插件
|
||||
if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
|
||||
throw exception(PLUGIN_STATUS_INVALID);
|
||||
}
|
||||
}
|
||||
pluginInfoDo.setStatus(status);
|
||||
pluginInfoMapper.updateById(pluginInfoDo);
|
||||
}
|
||||
|
||||
// @PostConstruct
|
||||
// public void init() {
|
||||
// Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
|
||||
// }
|
||||
//
|
||||
// @SneakyThrows
|
||||
// private void startPlugins() {
|
||||
// for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
|
||||
// if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
|
||||
// continue;
|
||||
// }
|
||||
// log.info("start plugin:{}", pluginInfoDO.getPluginId());
|
||||
// try {
|
||||
// pluginManager.startPlugin(pluginInfoDO.getPluginId());
|
||||
// } catch (Exception e) {
|
||||
// log.error("start plugin error", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugininstance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* IoT 插件实例 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PluginInstanceService {
|
||||
|
||||
/**
|
||||
* 创建IoT 插件实例
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createPluginInstance(@Valid PluginInstanceSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新IoT 插件实例
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updatePluginInstance(@Valid PluginInstanceSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除IoT 插件实例
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deletePluginInstance(Long id);
|
||||
|
||||
/**
|
||||
* 获得IoT 插件实例
|
||||
*
|
||||
* @param id 编号
|
||||
* @return IoT 插件实例
|
||||
*/
|
||||
PluginInstanceDO getPluginInstance(Long id);
|
||||
|
||||
/**
|
||||
* 获得IoT 插件实例分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return IoT 插件实例分页
|
||||
*/
|
||||
PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO);
|
||||
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.plugininstance;
|
||||
|
||||
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.plugininstance.vo.PluginInstancePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* IoT 插件实例 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||
|
||||
@Resource
|
||||
private PluginInstanceMapper pluginInstanceMapper;
|
||||
|
||||
@Override
|
||||
public Long createPluginInstance(PluginInstanceSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
PluginInstanceDO pluginInstance = BeanUtils.toBean(createReqVO, PluginInstanceDO.class);
|
||||
pluginInstanceMapper.insert(pluginInstance);
|
||||
// 返回
|
||||
return pluginInstance.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePluginInstance(PluginInstanceSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validatePluginInstanceExists(updateReqVO.getId());
|
||||
// 更新
|
||||
PluginInstanceDO updateObj = BeanUtils.toBean(updateReqVO, PluginInstanceDO.class);
|
||||
pluginInstanceMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePluginInstance(Long id) {
|
||||
// 校验存在
|
||||
validatePluginInstanceExists(id);
|
||||
// 删除
|
||||
pluginInstanceMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validatePluginInstanceExists(Long id) {
|
||||
if (pluginInstanceMapper.selectById(id) == null) {
|
||||
throw exception(PLUGIN_INSTANCE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginInstanceDO getPluginInstance(Long id) {
|
||||
return pluginInstanceMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO) {
|
||||
return pluginInstanceMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
}
|
@ -2,13 +2,14 @@ package cn.iocoder.yudao.module.iot.service.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
|
||||
import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@ -33,19 +34,23 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
@Resource
|
||||
private IotProductMapper productMapper;
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,解决循环依赖
|
||||
private IotProductThingModelService thingModelFunctionService;
|
||||
@Resource
|
||||
@Lazy // 延迟加载,解决循环依赖
|
||||
private IotThingModelMessageService thingModelMessageService;
|
||||
@Resource
|
||||
@Lazy // 延迟加载,解决循环依赖
|
||||
private IotDevicePropertyDataService devicePropertyDataService;
|
||||
|
||||
@Override
|
||||
public Long createProduct(IotProductSaveReqVO createReqVO) {
|
||||
// 1. 生成 ProductKey
|
||||
if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
|
||||
throw exception(PRODUCT_KEY_EXISTS);
|
||||
}
|
||||
// 1. 校验 ProductKey
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 为什么忽略租户?避免多个租户之间,productKey 重复,导致 TDengine 设备属性表重复
|
||||
if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
|
||||
throw exception(PRODUCT_KEY_EXISTS);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 插入
|
||||
IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
|
||||
.setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
|
||||
@ -119,8 +124,8 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
// 3. 产品是发布状态
|
||||
if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) {
|
||||
// 3.1 创建产品超级表数据模型
|
||||
thingModelFunctionService.createSuperTableDataModel(id);
|
||||
// 3.2 创建物模型日志超级表数据模型
|
||||
devicePropertyDataService.defineDevicePropertyData(id);
|
||||
// 3.2 创建物模型日志超级表数据模型 TODO 待定:message 要不要分;
|
||||
thingModelMessageService.createSuperTable(id);
|
||||
}
|
||||
productMapper.updateById(updateObj);
|
||||
|
@ -1,19 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.tdengine;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 超级表服务,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
|
||||
*/
|
||||
public interface IotSuperTableService {
|
||||
|
||||
/**
|
||||
* 创建超级表数据模型
|
||||
*/
|
||||
void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList);
|
||||
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.tdengine;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelRespVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* IoT 超级表服务实现类,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class IotSuperTableServiceImpl implements IotSuperTableService {
|
||||
|
||||
@Resource
|
||||
private TdEngineDDLMapper tdEngineDDLMapper;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
|
||||
private String url;
|
||||
|
||||
@Override
|
||||
public void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
|
||||
ThingModelRespVO thingModel = buildThingModel(product, thingModelList);
|
||||
|
||||
if (thingModel.getModel() == null || CollUtil.isEmpty(thingModel.getModel().getProperties())) {
|
||||
log.warn("物模型属性列表为空,不创建超级表");
|
||||
return;
|
||||
}
|
||||
|
||||
String superTableName = getSuperTableName(product.getDeviceType(), product.getProductKey());
|
||||
String databaseName = getDatabaseName();
|
||||
|
||||
List<Map<String, Object>> results = tdEngineDDLMapper.showSuperTables(new TdTableDO(databaseName, superTableName));
|
||||
int tableExists = results == null || results.isEmpty() ? 0 : results.size();
|
||||
if (tableExists > 0) {
|
||||
updateSuperTable(thingModel, product.getDeviceType());
|
||||
} else {
|
||||
createSuperTable(thingModel, product.getDeviceType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建超级表
|
||||
*/
|
||||
private void createSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
|
||||
// 解析物模型,获取字段列表
|
||||
List<TdFieldDO> schemaFields = new ArrayList<>();
|
||||
schemaFields.add(TdFieldDO.builder()
|
||||
.fieldName("time")
|
||||
.dataType("TIMESTAMP")
|
||||
.build());
|
||||
schemaFields.addAll(FieldParser.parse(thingModel));
|
||||
|
||||
// 设置超级表的标签
|
||||
List<TdFieldDO> tagsFields = List.of(
|
||||
TdFieldDO.builder().fieldName("product_key").dataType("NCHAR").dataLength(64).build(),
|
||||
TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build(),
|
||||
TdFieldDO.builder().fieldName("device_name").dataType("NCHAR").dataLength(64).build(),
|
||||
TdFieldDO.builder().fieldName("device_type").dataType("INT").build()
|
||||
);
|
||||
|
||||
// 获取超级表的名称和数据库名称
|
||||
String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
|
||||
String databaseName = getDatabaseName();
|
||||
|
||||
// 创建超级表
|
||||
tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新超级表
|
||||
*/
|
||||
private void updateSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
|
||||
String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
|
||||
try {
|
||||
List<TdFieldDO> oldFields = getTableFields(superTableName);
|
||||
List<TdFieldDO> newFields = FieldParser.parse(thingModel);
|
||||
|
||||
updateTableFields(superTableName, oldFields, newFields);
|
||||
} catch (Exception e) {
|
||||
log.error("更新物模型超级表失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表的字段信息
|
||||
*/
|
||||
private List<TdFieldDO> getTableFields(String tableName) {
|
||||
List<Map<String, Object>> tableDescription = tdEngineDDLMapper.describeSuperTable(new TdTableDO(getDatabaseName(), tableName));
|
||||
if (CollUtil.isEmpty(tableDescription)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return tableDescription.stream()
|
||||
.filter(map -> !"TAG".equals(map.get("note")))
|
||||
.filter(map -> !"time".equals(map.get("field")))
|
||||
.map(map -> TdFieldDO.builder()
|
||||
.fieldName((String) map.get("field"))
|
||||
.dataType((String) map.get("type"))
|
||||
.dataLength((Integer) map.get("length"))
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新表的字段,包括新增、修改和删除字段
|
||||
*/
|
||||
private void updateTableFields(String tableName, List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
|
||||
String databaseName = getDatabaseName();
|
||||
|
||||
// 获取新增、修改、删除的字段
|
||||
List<TdFieldDO> addFields = getAddFields(oldFields, newFields);
|
||||
List<TdFieldDO> modifyFields = getModifyFields(oldFields, newFields);
|
||||
List<TdFieldDO> dropFields = getDropFields(oldFields, newFields);
|
||||
|
||||
// 添加新增字段
|
||||
if (CollUtil.isNotEmpty(addFields)) {
|
||||
for (TdFieldDO addField : addFields) {
|
||||
tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
|
||||
.dataBaseName(databaseName)
|
||||
.superTableName(tableName)
|
||||
.column(addField)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
// 删除旧字段
|
||||
if (CollUtil.isNotEmpty(dropFields)) {
|
||||
for (TdFieldDO dropField : dropFields) {
|
||||
tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
|
||||
.dataBaseName(databaseName)
|
||||
.superTableName(tableName)
|
||||
.column(dropField)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
// 修改字段(先删除再添加)
|
||||
if (CollUtil.isNotEmpty(modifyFields)) {
|
||||
for (TdFieldDO modifyField : modifyFields) {
|
||||
tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
|
||||
.dataBaseName(databaseName)
|
||||
.superTableName(tableName)
|
||||
.column(modifyField)
|
||||
.build());
|
||||
tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
|
||||
.dataBaseName(databaseName)
|
||||
.superTableName(tableName)
|
||||
.column(modifyField)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要新增的字段
|
||||
*/
|
||||
private List<TdFieldDO> getAddFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
|
||||
Set<String> oldFieldNames = oldFields.stream()
|
||||
.map(TdFieldDO::getFieldName)
|
||||
.collect(Collectors.toSet());
|
||||
return newFields.stream()
|
||||
.filter(f -> !oldFieldNames.contains(f.getFieldName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要修改的字段
|
||||
*/
|
||||
private List<TdFieldDO> getModifyFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
|
||||
Map<String, TdFieldDO> oldFieldMap = oldFields.stream()
|
||||
.collect(Collectors.toMap(TdFieldDO::getFieldName, f -> f));
|
||||
|
||||
return newFields.stream()
|
||||
.filter(f -> {
|
||||
TdFieldDO oldField = oldFieldMap.get(f.getFieldName());
|
||||
return oldField != null && (
|
||||
!oldField.getDataType().equals(f.getDataType()) ||
|
||||
!Objects.equals(oldField.getDataLength(), f.getDataLength())
|
||||
);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要删除的字段
|
||||
*/
|
||||
private List<TdFieldDO> getDropFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
|
||||
Set<String> newFieldNames = newFields.stream()
|
||||
.map(TdFieldDO::getFieldName)
|
||||
.collect(Collectors.toSet());
|
||||
return oldFields.stream()
|
||||
.filter(f -> !"time".equals(f.getFieldName()))
|
||||
.filter(f -> !newFieldNames.contains(f.getFieldName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建物模型
|
||||
*/
|
||||
private ThingModelRespVO buildThingModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
|
||||
ThingModelRespVO thingModel = new ThingModelRespVO();
|
||||
thingModel.setId(product.getId());
|
||||
thingModel.setProductKey(product.getProductKey());
|
||||
|
||||
List<ThingModelProperty> properties = thingModelList.stream()
|
||||
.filter(item -> IotProductThingModelTypeEnum.PROPERTY.equals(
|
||||
IotProductThingModelTypeEnum.valueOfType(item.getType())))
|
||||
.map(this::buildThingModelProperty)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
ThingModelRespVO.Model model = new ThingModelRespVO.Model();
|
||||
model.setProperties(properties);
|
||||
thingModel.setModel(model);
|
||||
|
||||
return thingModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建物模型属性
|
||||
*/
|
||||
private ThingModelProperty buildThingModelProperty(IotProductThingModelDO thingModel) {
|
||||
ThingModelProperty property = BeanUtil.copyProperties(thingModel, ThingModelProperty.class);
|
||||
property.setDataType(thingModel.getProperty().getDataType());
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库名称
|
||||
*/
|
||||
private String getDatabaseName() {
|
||||
int index = url.lastIndexOf("/");
|
||||
return index != -1 ? url.substring(index + 1) : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取超级表名称
|
||||
*/
|
||||
private String getSuperTableName(Integer deviceType, String productKey) {
|
||||
String prefix = switch (deviceType) {
|
||||
case 1 -> "gateway_sub_";
|
||||
case 2 -> "gateway_";
|
||||
default -> "device_";
|
||||
};
|
||||
return (prefix + productKey).toLowerCase();
|
||||
}
|
||||
|
||||
}
|
@ -11,16 +11,16 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.IotConstants;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -52,7 +52,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
|
||||
private String url;
|
||||
|
||||
@Resource
|
||||
private IotProductThingModelService iotProductThingModelService;
|
||||
private IotThingModelService iotThingModelService;
|
||||
@Resource
|
||||
private IotDeviceService iotDeviceService;
|
||||
@Resource
|
||||
@ -83,7 +83,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
|
||||
|
||||
// 2. 获取设备属性并进行物模型校验,过滤非物模型属性
|
||||
Map<String, Object> params = thingModelMessage.dataToMap();
|
||||
List<IotProductThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
|
||||
List<IotThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
|
||||
if (thingModelList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -102,9 +102,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
|
||||
.build());
|
||||
}
|
||||
|
||||
private List<IotProductThingModelDO> getValidThingModelList(String productKey) {
|
||||
return filterList(iotProductThingModelService.getProductThingModelListByProductKey(productKey),
|
||||
thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
|
||||
private List<IotThingModelDO> getValidThingModelList(String productKey) {
|
||||
return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
|
||||
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -134,21 +134,17 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
|
||||
tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
|
||||
}
|
||||
|
||||
private List<IotProductThingModelDO> getValidFunctionList(String productKey) {
|
||||
// TODO @puhui999:使用 convertList 会好点哈
|
||||
return iotProductThingModelService
|
||||
.getProductThingModelListByProductKey(productKey)
|
||||
.stream()
|
||||
.filter(function -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(function.getType()))
|
||||
.toList();
|
||||
private List<IotThingModelDO> getValidFunctionList(String productKey) {
|
||||
return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
|
||||
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
|
||||
}
|
||||
|
||||
private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotProductThingModelDO> thingModelList, IotDeviceDO device, Long time) {
|
||||
private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotThingModelDO> thingModelList, IotDeviceDO device, Long time) {
|
||||
// 1. 获取属性标识符集合
|
||||
Set<String> propertyIdentifiers = convertSet(thingModelList, IotProductThingModelDO::getIdentifier);
|
||||
Set<String> propertyIdentifiers = convertSet(thingModelList, IotThingModelDO::getIdentifier);
|
||||
|
||||
// 2. 构建属性标识符和属性的映射
|
||||
Map<String, IotProductThingModelDO> thingModelMap = convertMap(thingModelList, IotProductThingModelDO::getIdentifier);
|
||||
Map<String, IotThingModelDO> thingModelMap = convertMap(thingModelList, IotThingModelDO::getIdentifier);
|
||||
|
||||
// 3. 过滤并收集有效的属性字段
|
||||
List<TdFieldDO> schemaFieldValues = new ArrayList<>();
|
||||
@ -168,21 +164,21 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
|
||||
* 缓存设备属性
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param iotProductThingModelDO 物模型属性
|
||||
* @param iotThingModelDO 物模型属性
|
||||
* @param val 属性值
|
||||
* @param time 时间
|
||||
*/
|
||||
private void setDeviceDataCache(IotDeviceDO device, IotProductThingModelDO iotProductThingModelDO, Object val, Long time) {
|
||||
private void setDeviceDataCache(IotDeviceDO device, IotThingModelDO iotThingModelDO, Object val, Long time) {
|
||||
IotDeviceDataDO deviceData = IotDeviceDataDO.builder()
|
||||
.productKey(device.getProductKey())
|
||||
.deviceName(device.getDeviceName())
|
||||
.identifier(iotProductThingModelDO.getIdentifier())
|
||||
.identifier(iotThingModelDO.getIdentifier())
|
||||
.value(val != null ? val.toString() : null)
|
||||
.updateTime(DateUtil.toLocalDateTime(new Date(time)))
|
||||
.deviceId(device.getId())
|
||||
.thingModelId(iotProductThingModelDO.getId())
|
||||
.name(iotProductThingModelDO.getName())
|
||||
.dataType(iotProductThingModelDO.getProperty().getDataType())
|
||||
.thingModelId(iotThingModelDO.getId())
|
||||
.name(iotThingModelDO.getName())
|
||||
.dataType(iotThingModelDO.getProperty().getDataType())
|
||||
.build();
|
||||
deviceDataRedisDAO.set(deviceData);
|
||||
}
|
||||
|
@ -1,442 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.thingmodel;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
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 cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArrayDataSpecs;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.convert.thingmodel.IotProductThingModelConvert;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotProductThingModelMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.tdengine.IotSuperTableService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* IoT 产品物模型 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotProductThingModelServiceImpl implements IotProductThingModelService {
|
||||
|
||||
@Resource
|
||||
private IotProductThingModelMapper productThingModelMapper;
|
||||
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
@Resource
|
||||
private IotSuperTableService dbStructureDataService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createProductThingModel(IotProductThingModelSaveReqVO createReqVO) {
|
||||
// 1. 校验功能标识符在同一产品下是否唯一
|
||||
validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier());
|
||||
|
||||
// 2. 功能名称在同一产品下是否唯一
|
||||
validateNameUnique(createReqVO.getProductId(), createReqVO.getName());
|
||||
|
||||
// 3. 系统保留字段,不能用于标识符定义
|
||||
validateNotDefaultEventAndService(createReqVO.getIdentifier());
|
||||
|
||||
// 4. 校验产品状态,发布状态下,不允许新增功能
|
||||
validateProductStatus(createReqVO.getProductId());
|
||||
|
||||
// 5. 插入数据库
|
||||
IotProductThingModelDO thingModel = IotProductThingModelConvert.INSTANCE.convert(createReqVO);
|
||||
productThingModelMapper.insert(thingModel);
|
||||
|
||||
// 6. 如果创建的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(createReqVO.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
|
||||
}
|
||||
return thingModel.getId();
|
||||
}
|
||||
|
||||
private void validateProductStatus(Long createReqVO) {
|
||||
IotProductDO product = productService.getProduct(createReqVO);
|
||||
if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
|
||||
throw exception(PRODUCT_STATUS_NOT_ALLOW_THING_MODEL);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNotDefaultEventAndService(String identifier) {
|
||||
// set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义
|
||||
if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) {
|
||||
throw exception(THING_MODEL_IDENTIFIER_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNameUnique(Long productId, String name) {
|
||||
IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndName(productId, name);
|
||||
if (thingModel != null) {
|
||||
throw exception(THING_MODEL_NAME_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIdentifierUnique(Long productId, String identifier) {
|
||||
IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
|
||||
if (thingModel != null) {
|
||||
throw exception(THING_MODEL_IDENTIFIER_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateProductThingModel(IotProductThingModelSaveReqVO updateReqVO) {
|
||||
// 1. 校验功能是否存在
|
||||
validateProductThingModelMapperExists(updateReqVO.getId());
|
||||
|
||||
// 2. 校验功能标识符是否唯一
|
||||
validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
|
||||
|
||||
// 3. 校验产品状态,发布状态下,不允许操作功能
|
||||
validateProductStatus(updateReqVO.getProductId());
|
||||
|
||||
// 4. 更新数据库
|
||||
IotProductThingModelDO thingModel = IotProductThingModelConvert.INSTANCE.convert(updateReqVO);
|
||||
productThingModelMapper.updateById(thingModel);
|
||||
|
||||
// 5. 如果更新的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(updateReqVO.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) {
|
||||
IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
|
||||
if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
|
||||
throw exception(THING_MODEL_IDENTIFIER_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteProductThingModel(Long id) {
|
||||
// 1. 校验功能是否存在
|
||||
IotProductThingModelDO thingModel = productThingModelMapper.selectById(id);
|
||||
if (thingModel == null) {
|
||||
throw exception(THING_MODEL_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 3. 校验产品状态,发布状态下,不允许操作功能
|
||||
validateProductStatus(thingModel.getProductId());
|
||||
|
||||
// 2. 删除功能
|
||||
productThingModelMapper.deleteById(id);
|
||||
|
||||
// 3. 如果删除的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(thingModel.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验功能是否存在
|
||||
*
|
||||
* @param id 功能编号
|
||||
*/
|
||||
private void validateProductThingModelMapperExists(Long id) {
|
||||
if (productThingModelMapper.selectById(id) == null) {
|
||||
throw exception(THING_MODEL_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotProductThingModelDO getProductThingModel(Long id) {
|
||||
return productThingModelMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotProductThingModelDO> getProductThingModelListByProductId(Long productId) {
|
||||
return productThingModelMapper.selectListByProductId(productId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotProductThingModelDO> getProductThingModelPage(IotProductThingModelPageReqVO pageReqVO) {
|
||||
return productThingModelMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSuperTableDataModel(Long productId) {
|
||||
// 1. 查询产品
|
||||
IotProductDO product = productService.getProduct(productId);
|
||||
|
||||
// 2. 查询产品的物模型功能列表
|
||||
List<IotProductThingModelDO> thingModelList = productThingModelMapper.selectListByProductId(productId);
|
||||
|
||||
// 3. 生成 TDengine 的数据模型
|
||||
dbStructureDataService.createSuperTableDataModel(product, thingModelList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotProductThingModelDO> getProductThingModelListByProductKey(String productKey) {
|
||||
return productThingModelMapper.selectListByProductKey(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认的事件和服务
|
||||
*/
|
||||
public void createDefaultEventsAndServices(Long productId, String productKey) {
|
||||
// 1. 获取当前属性列表
|
||||
List<IotProductThingModelDO> propertyList = productThingModelMapper
|
||||
.selectListByProductIdAndType(productId, IotProductThingModelTypeEnum.PROPERTY.getType());
|
||||
|
||||
// 2. 生成新的事件和服务列表
|
||||
List<IotProductThingModelDO> newThingModelList = new ArrayList<>();
|
||||
// 生成属性上报事件
|
||||
ThingModelEvent propertyPostEvent = generatePropertyPostEvent(propertyList);
|
||||
if (propertyPostEvent != null) {
|
||||
IotProductThingModelDO eventThingModel = buildEventThingModelDO(productId, productKey, propertyPostEvent);
|
||||
newThingModelList.add(eventThingModel);
|
||||
}
|
||||
// 生成属性设置服务
|
||||
ThingModelService propertySetService = generatePropertySetService(propertyList);
|
||||
if (propertySetService != null) {
|
||||
IotProductThingModelDO setServiceThingModel = buildServiceThingModelDO(productId, productKey, propertySetService);
|
||||
newThingModelList.add(setServiceThingModel);
|
||||
}
|
||||
// 生成属性获取服务
|
||||
ThingModelService propertyGetService = generatePropertyGetService(propertyList);
|
||||
if (propertyGetService != null) {
|
||||
IotProductThingModelDO getServiceThingModel = buildServiceThingModelDO(productId, productKey, propertyGetService);
|
||||
newThingModelList.add(getServiceThingModel);
|
||||
}
|
||||
|
||||
// 3. 获取数据库中的默认的旧事件和服务列表
|
||||
List<IotProductThingModelDO> oldThingModelList = productThingModelMapper.selectListByProductIdAndIdentifiersAndTypes(
|
||||
productId,
|
||||
Arrays.asList("post", "set", "get"),
|
||||
Arrays.asList(IotProductThingModelTypeEnum.EVENT.getType(), IotProductThingModelTypeEnum.SERVICE.getType())
|
||||
);
|
||||
|
||||
// 3.1 使用 diffList 方法比较新旧列表
|
||||
List<List<IotProductThingModelDO>> diffResult = diffList(oldThingModelList, newThingModelList,
|
||||
// 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。
|
||||
(oldFunc, newFunc) -> Objects.equals(oldFunc.getIdentifier(), newFunc.getIdentifier())
|
||||
&& Objects.equals(oldFunc.getType(), newFunc.getType()));
|
||||
List<IotProductThingModelDO> createList = diffResult.get(0); // 需要新增的
|
||||
List<IotProductThingModelDO> updateList = diffResult.get(1); // 需要更新的
|
||||
List<IotProductThingModelDO> deleteList = diffResult.get(2); // 需要删除的
|
||||
|
||||
// 3.2 批量执行数据库操作
|
||||
// 新增数据库中的新事件和服务列表
|
||||
if (CollUtil.isNotEmpty(createList)) {
|
||||
productThingModelMapper.insertBatch(createList);
|
||||
}
|
||||
// 更新数据库中的事件和服务列表
|
||||
if (CollUtil.isNotEmpty(updateList)) {
|
||||
// 首先,为每个需要更新的对象设置其对应的 ID
|
||||
updateList.forEach(updateFunc -> {
|
||||
IotProductThingModelDO oldFunc = findThingModelByIdentifierAndType(
|
||||
oldThingModelList, updateFunc.getIdentifier(), updateFunc.getType());
|
||||
if (oldFunc != null) {
|
||||
updateFunc.setId(oldFunc.getId());
|
||||
}
|
||||
});
|
||||
// 过滤掉没有设置 ID 的对象
|
||||
List<IotProductThingModelDO> validUpdateList = updateList.stream()
|
||||
.filter(func -> func.getId() != null)
|
||||
.collect(Collectors.toList());
|
||||
// 执行批量更新
|
||||
if (CollUtil.isNotEmpty(validUpdateList)) {
|
||||
productThingModelMapper.updateBatch(validUpdateList);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库中的旧事件和服务列表
|
||||
if (CollUtil.isNotEmpty(deleteList)) {
|
||||
Set<Long> idsToDelete = CollectionUtils.convertSet(deleteList, IotProductThingModelDO::getId);
|
||||
productThingModelMapper.deleteByIds(idsToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识符和类型查找功能对象
|
||||
*/
|
||||
private IotProductThingModelDO findThingModelByIdentifierAndType(List<IotProductThingModelDO> thingModelList,
|
||||
String identifier, Integer type) {
|
||||
return CollUtil.findOne(thingModelList, func ->
|
||||
Objects.equals(func.getIdentifier(), identifier) && Objects.equals(func.getType(), type));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建事件功能对象
|
||||
*/
|
||||
private IotProductThingModelDO buildEventThingModelDO(Long productId, String productKey, ThingModelEvent event) {
|
||||
return new IotProductThingModelDO()
|
||||
.setProductId(productId)
|
||||
.setProductKey(productKey)
|
||||
.setIdentifier(event.getIdentifier())
|
||||
.setName(event.getName())
|
||||
.setDescription(event.getDescription())
|
||||
.setType(IotProductThingModelTypeEnum.EVENT.getType())
|
||||
.setEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建服务功能对象
|
||||
*/
|
||||
private IotProductThingModelDO buildServiceThingModelDO(Long productId, String productKey, ThingModelService service) {
|
||||
return new IotProductThingModelDO()
|
||||
.setProductId(productId)
|
||||
.setProductKey(productKey)
|
||||
.setIdentifier(service.getIdentifier())
|
||||
.setName(service.getName())
|
||||
.setDescription(service.getDescription())
|
||||
.setType(IotProductThingModelTypeEnum.SERVICE.getType())
|
||||
.setService(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成属性上报事件
|
||||
*/
|
||||
private ThingModelEvent generatePropertyPostEvent(List<IotProductThingModelDO> propertyList) {
|
||||
if (CollUtil.isEmpty(propertyList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingModelEvent event = new ThingModelEvent()
|
||||
.setIdentifier("post")
|
||||
.setName("属性上报")
|
||||
.setType("info")
|
||||
.setDescription("属性上报事件")
|
||||
.setMethod("thing.event.property.post");
|
||||
|
||||
// 将属性列表转换为事件的输出参数
|
||||
List<ThingModelArgument> outputData = new ArrayList<>();
|
||||
for (IotProductThingModelDO thingModel : propertyList) {
|
||||
ThingModelArgument arg = new ThingModelArgument()
|
||||
.setIdentifier(thingModel.getIdentifier())
|
||||
.setName(thingModel.getName())
|
||||
.setProperty(thingModel.getProperty())
|
||||
.setDescription(thingModel.getDescription())
|
||||
.setDirection("output"); // 设置为输出参数
|
||||
outputData.add(arg);
|
||||
}
|
||||
event.setOutputData(outputData);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成属性设置服务
|
||||
*/
|
||||
private ThingModelService generatePropertySetService(List<IotProductThingModelDO> propertyList) {
|
||||
if (propertyList == null || propertyList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ThingModelArgument> inputData = new ArrayList<>();
|
||||
for (IotProductThingModelDO thingModel : propertyList) {
|
||||
ThingModelProperty property = thingModel.getProperty();
|
||||
if (IotProductThingModelAccessModeEnum.READ_WRITE.getMode().equals(property.getAccessMode())) {
|
||||
ThingModelArgument arg = new ThingModelArgument()
|
||||
.setIdentifier(property.getIdentifier())
|
||||
.setName(property.getName())
|
||||
.setProperty(property)
|
||||
.setDescription(property.getDescription())
|
||||
.setDirection("input"); // 设置为输入参数
|
||||
inputData.add(arg);
|
||||
}
|
||||
}
|
||||
if (inputData.isEmpty()) {
|
||||
// 如果没有可写属性,不生成属性设置服务
|
||||
return null;
|
||||
}
|
||||
|
||||
// 属性设置服务一般不需要输出参数
|
||||
return new ThingModelService()
|
||||
.setIdentifier("set")
|
||||
.setName("属性设置")
|
||||
.setCallType("async")
|
||||
.setDescription("属性设置服务")
|
||||
.setMethod("thing.service.property.set")
|
||||
.setInputData(inputData)
|
||||
// 属性设置服务一般不需要输出参数
|
||||
.setOutputData(new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成属性获取服务
|
||||
*/
|
||||
private ThingModelService generatePropertyGetService(List<IotProductThingModelDO> propertyList) {
|
||||
if (propertyList == null || propertyList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ThingModelArgument> outputData = new ArrayList<>();
|
||||
for (IotProductThingModelDO thingModelDO : propertyList) {
|
||||
ThingModelProperty property = thingModelDO.getProperty();
|
||||
if (ObjectUtils.equalsAny(property.getAccessMode(),
|
||||
IotProductThingModelAccessModeEnum.READ_ONLY.getMode(), IotProductThingModelAccessModeEnum.READ_WRITE.getMode())) {
|
||||
ThingModelArgument arg = new ThingModelArgument()
|
||||
.setIdentifier(property.getIdentifier())
|
||||
.setName(property.getName())
|
||||
.setProperty(property)
|
||||
.setDescription(property.getDescription())
|
||||
.setDirection("output"); // 设置为输出参数
|
||||
outputData.add(arg);
|
||||
}
|
||||
}
|
||||
if (outputData.isEmpty()) {
|
||||
// 如果没有可读属性,不生成属性获取服务
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingModelService service = new ThingModelService()
|
||||
.setIdentifier("get")
|
||||
.setName("属性获取")
|
||||
.setCallType("async")
|
||||
.setDescription("属性获取服务")
|
||||
.setMethod("thing.service.property.get");
|
||||
|
||||
// 定义输入参数:属性标识符列表
|
||||
ThingModelArgument inputArg = new ThingModelArgument()
|
||||
.setIdentifier("properties")
|
||||
.setName("属性标识符列表")
|
||||
.setDescription("需要获取的属性标识符列表")
|
||||
.setDirection("input"); // 设置为输入参数
|
||||
|
||||
// 创建数组类型,元素类型为文本类型(字符串)TODO @puhui999: 还得研究研究
|
||||
ThingModelArrayDataSpecs arrayType = new ThingModelArrayDataSpecs();
|
||||
arrayType.setDataType("array");
|
||||
inputArg.setProperty(new ThingModelProperty().setIdentifier(inputArg.getIdentifier()).setName(inputArg.getName())
|
||||
.setDescription(inputArg.getDescription()).setDataSpecs(arrayType));
|
||||
|
||||
ThingModelDateOrTextDataSpecs textType = new ThingModelDateOrTextDataSpecs();
|
||||
textType.setDataType("text");
|
||||
inputArg.setProperty(new ThingModelProperty().setIdentifier(inputArg.getIdentifier()).setName(inputArg.getName())
|
||||
.setDescription(inputArg.getDescription()).setDataSpecs(textType));
|
||||
|
||||
service.setInputData(Collections.singletonList(inputArg));
|
||||
service.setOutputData(outputData);
|
||||
return service;
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package cn.iocoder.yudao.module.iot.service.thingmodel;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
@ -13,7 +13,7 @@ import java.util.List;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotProductThingModelService {
|
||||
public interface IotThingModelService {
|
||||
|
||||
/**
|
||||
* 创建产品物模型
|
||||
@ -21,21 +21,21 @@ public interface IotProductThingModelService {
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createProductThingModel(@Valid IotProductThingModelSaveReqVO createReqVO);
|
||||
Long createThingModel(@Valid IotThingModelSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新产品物模型
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateProductThingModel(@Valid IotProductThingModelSaveReqVO updateReqVO);
|
||||
void updateThingModel(@Valid IotThingModelSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除产品物模型
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteProductThingModel(Long id);
|
||||
void deleteThingModel(Long id);
|
||||
|
||||
/**
|
||||
* 获得产品物模型
|
||||
@ -43,7 +43,7 @@ public interface IotProductThingModelService {
|
||||
* @param id 编号
|
||||
* @return 产品物模型
|
||||
*/
|
||||
IotProductThingModelDO getProductThingModel(Long id);
|
||||
IotThingModelDO getThingModel(Long id);
|
||||
|
||||
/**
|
||||
* 获得产品物模型列表
|
||||
@ -51,7 +51,7 @@ public interface IotProductThingModelService {
|
||||
* @param productId 产品编号
|
||||
* @return 产品物模型列表
|
||||
*/
|
||||
List<IotProductThingModelDO> getProductThingModelListByProductId(Long productId);
|
||||
List<IotThingModelDO> getThingModelListByProductId(Long productId);
|
||||
|
||||
/**
|
||||
* 获得产品物模型分页
|
||||
@ -59,14 +59,7 @@ public interface IotProductThingModelService {
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 产品物模型分页
|
||||
*/
|
||||
PageResult<IotProductThingModelDO> getProductThingModelPage(IotProductThingModelPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 创建超级表数据模型
|
||||
*
|
||||
* @param productId 产品编号
|
||||
*/
|
||||
void createSuperTableDataModel(Long productId);
|
||||
PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得产品物模型列表
|
||||
@ -74,6 +67,6 @@ public interface IotProductThingModelService {
|
||||
* @param productKey 产品 Key
|
||||
* @return 产品物模型列表
|
||||
*/
|
||||
List<IotProductThingModelDO> getProductThingModelListByProductKey(String productKey);
|
||||
List<IotThingModelDO> getProductThingModelListByProductKey(String productKey);
|
||||
|
||||
}
|
@ -0,0 +1,330 @@
|
||||
package cn.iocoder.yudao.module.iot.service.thingmodel;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
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.model.ThingModelEvent;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.thingmodel.*;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* IoT 产品物模型 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotThingModelServiceImpl implements IotThingModelService {
|
||||
|
||||
@Resource
|
||||
private IotThingModelMapper thingModelMapper;
|
||||
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createThingModel(IotThingModelSaveReqVO createReqVO) {
|
||||
// 1. 校验功能标识符在同一产品下是否唯一
|
||||
validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier());
|
||||
|
||||
// 2. 功能名称在同一产品下是否唯一
|
||||
validateNameUnique(createReqVO.getProductId(), createReqVO.getName());
|
||||
|
||||
// 3. 系统保留字段,不能用于标识符定义
|
||||
validateNotDefaultEventAndService(createReqVO.getIdentifier());
|
||||
|
||||
// 4. 校验产品状态,发布状态下,不允许新增功能
|
||||
validateProductStatus(createReqVO.getProductId());
|
||||
|
||||
// 5. 插入数据库
|
||||
IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(createReqVO);
|
||||
thingModelMapper.insert(thingModel);
|
||||
|
||||
// 6. 如果创建的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(createReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
|
||||
}
|
||||
// TODO @puhui999: 服务和事件的情况 method 怎么设置?在前端设置还是后端设置?
|
||||
return thingModel.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateThingModel(IotThingModelSaveReqVO updateReqVO) {
|
||||
// 1. 校验功能是否存在
|
||||
validateProductThingModelMapperExists(updateReqVO.getId());
|
||||
|
||||
// 2. 校验功能标识符是否唯一
|
||||
validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
|
||||
|
||||
// 3. 校验产品状态,发布状态下,不允许操作功能
|
||||
validateProductStatus(updateReqVO.getProductId());
|
||||
|
||||
// 4. 更新数据库
|
||||
IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
|
||||
thingModelMapper.updateById(thingModel);
|
||||
|
||||
// 5. 如果更新的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteThingModel(Long id) {
|
||||
// 1. 校验功能是否存在
|
||||
IotThingModelDO thingModel = thingModelMapper.selectById(id);
|
||||
if (thingModel == null) {
|
||||
throw exception(THING_MODEL_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 3. 校验产品状态,发布状态下,不允许操作功能
|
||||
validateProductStatus(thingModel.getProductId());
|
||||
|
||||
// 2. 删除功能
|
||||
thingModelMapper.deleteById(id);
|
||||
|
||||
// 3. 如果删除的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotThingModelDO getThingModel(Long id) {
|
||||
return thingModelMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotThingModelDO> getThingModelListByProductId(Long productId) {
|
||||
return thingModelMapper.selectListByProductId(productId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
|
||||
return thingModelMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotThingModelDO> getProductThingModelListByProductKey(String productKey) {
|
||||
return thingModelMapper.selectListByProductKey(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验功能是否存在
|
||||
*
|
||||
* @param id 功能编号
|
||||
*/
|
||||
private void validateProductThingModelMapperExists(Long id) {
|
||||
if (thingModelMapper.selectById(id) == null) {
|
||||
throw exception(THING_MODEL_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) {
|
||||
IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
|
||||
if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
|
||||
throw exception(THING_MODEL_IDENTIFIER_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProductStatus(Long createReqVO) {
|
||||
IotProductDO product = productService.getProduct(createReqVO);
|
||||
if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
|
||||
throw exception(PRODUCT_STATUS_NOT_ALLOW_THING_MODEL);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNotDefaultEventAndService(String identifier) {
|
||||
// set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义
|
||||
if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) {
|
||||
throw exception(THING_MODEL_IDENTIFIER_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNameUnique(Long productId, String name) {
|
||||
IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndName(productId, name);
|
||||
if (thingModel != null) {
|
||||
throw exception(THING_MODEL_NAME_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIdentifierUnique(Long productId, String identifier) {
|
||||
IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
|
||||
if (thingModel != null) {
|
||||
throw exception(THING_MODEL_IDENTIFIER_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认的事件和服务
|
||||
*/
|
||||
public void createDefaultEventsAndServices(Long productId, String productKey) {
|
||||
// 1. 获取当前属性列表
|
||||
List<IotThingModelDO> properties = thingModelMapper
|
||||
.selectListByProductIdAndType(productId, IotThingModelTypeEnum.PROPERTY.getType());
|
||||
|
||||
// 2. 生成新的事件和服务列表
|
||||
List<IotThingModelDO> newThingModels = new ArrayList<>();
|
||||
// 2.1 生成属性上报事件
|
||||
ThingModelEvent propertyPostEvent = generatePropertyPostEvent(properties);
|
||||
if (propertyPostEvent != null) {
|
||||
newThingModels.add(buildEventThingModelDO(productId, productKey, propertyPostEvent, "属性上报事件"));
|
||||
}
|
||||
// 2.2 生成属性设置服务
|
||||
ThingModelService propertySetService = generatePropertySetService(properties);
|
||||
if (propertySetService != null) {
|
||||
newThingModels.add(buildServiceThingModelDO(productId, productKey, propertySetService, "属性设置服务"));
|
||||
}
|
||||
// 2.3 生成属性获取服务
|
||||
ThingModelService propertyGetService = generatePropertyGetService(properties);
|
||||
if (propertyGetService != null) {
|
||||
newThingModels.add(buildServiceThingModelDO(productId, productKey, propertyGetService, "属性获取服务"));
|
||||
}
|
||||
|
||||
// 3.1 获取数据库中的默认的旧事件和服务列表
|
||||
List<IotThingModelDO> oldThingModels = thingModelMapper.selectListByProductIdAndIdentifiersAndTypes(
|
||||
productId,
|
||||
Arrays.asList("post", "set", "get"),
|
||||
Arrays.asList(IotThingModelTypeEnum.EVENT.getType(), IotThingModelTypeEnum.SERVICE.getType())
|
||||
);
|
||||
// 3.2 创建默认的事件和服务
|
||||
createDefaultEventsAndServices(oldThingModels, newThingModels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认的事件和服务
|
||||
*/
|
||||
private void createDefaultEventsAndServices(List<IotThingModelDO> oldThingModels,
|
||||
List<IotThingModelDO> newThingModels) {
|
||||
// 使用 diffList 方法比较新旧列表
|
||||
List<List<IotThingModelDO>> diffResult = diffList(oldThingModels, newThingModels,
|
||||
(oldVal, newVal) -> {
|
||||
// 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。
|
||||
boolean same = Objects.equals(oldVal.getIdentifier(), newVal.getIdentifier())
|
||||
&& Objects.equals(oldVal.getType(), newVal.getType());
|
||||
if (same) {
|
||||
newVal.setId(oldVal.getId()); // 设置编号
|
||||
}
|
||||
return same;
|
||||
});
|
||||
// 批量添加、修改、删除
|
||||
if (CollUtil.isNotEmpty(diffResult.get(0))) {
|
||||
thingModelMapper.insertBatch(diffResult.get(0));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(diffResult.get(1))) {
|
||||
thingModelMapper.updateBatch(diffResult.get(1));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(diffResult.get(2))) {
|
||||
thingModelMapper.deleteByIds(convertSet(diffResult.get(2), IotThingModelDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建事件功能对象
|
||||
*/
|
||||
private IotThingModelDO buildEventThingModelDO(Long productId, String productKey,
|
||||
ThingModelEvent event, String description) {
|
||||
return new IotThingModelDO().setProductId(productId).setProductKey(productKey)
|
||||
.setIdentifier(event.getIdentifier()).setName(event.getName()).setDescription(description)
|
||||
.setType(IotThingModelTypeEnum.EVENT.getType()).setEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建服务功能对象
|
||||
*/
|
||||
private IotThingModelDO buildServiceThingModelDO(Long productId, String productKey,
|
||||
ThingModelService service, String description) {
|
||||
return new IotThingModelDO().setProductId(productId).setProductKey(productKey)
|
||||
.setIdentifier(service.getIdentifier()).setName(service.getName()).setDescription(description)
|
||||
.setType(IotThingModelTypeEnum.SERVICE.getType()).setService(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成属性上报事件
|
||||
*/
|
||||
private ThingModelEvent generatePropertyPostEvent(List<IotThingModelDO> thingModels) {
|
||||
// 没有属性则不生成
|
||||
if (CollUtil.isEmpty(thingModels)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成属性上报事件
|
||||
return new ThingModelEvent().setIdentifier("post").setName("属性上报").setMethod("thing.event.property.post")
|
||||
.setType(IotThingModelServiceEventTypeEnum.INFO.getType())
|
||||
.setOutputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.OUTPUT));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成属性设置服务
|
||||
*/
|
||||
private ThingModelService generatePropertySetService(List<IotThingModelDO> thingModels) {
|
||||
// 1.1 过滤出所有可写属性
|
||||
thingModels = filterList(thingModels, thingModel ->
|
||||
IotThingModelAccessModeEnum.READ_WRITE.getMode().equals(thingModel.getProperty().getAccessMode()));
|
||||
// 1.2 没有可写属性则不生成
|
||||
if (CollUtil.isEmpty(thingModels)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 生成属性设置服务
|
||||
return new ThingModelService().setIdentifier("set").setName("属性设置").setMethod("thing.service.property.set")
|
||||
.setCallType(IotThingModelServiceCallTypeEnum.ASYNC.getType())
|
||||
.setInputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.INPUT))
|
||||
.setOutputParams(Collections.emptyList()); // 属性设置服务一般不需要输出参数
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成属性获取服务
|
||||
*/
|
||||
private ThingModelService generatePropertyGetService(List<IotThingModelDO> thingModels) {
|
||||
// 1.1 没有属性则不生成
|
||||
if (CollUtil.isEmpty(thingModels)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1.2 生成属性获取服务
|
||||
return new ThingModelService().setIdentifier("get").setName("属性获取").setMethod("thing.service.property.get")
|
||||
.setCallType(IotThingModelServiceCallTypeEnum.ASYNC.getType())
|
||||
.setInputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.INPUT))
|
||||
.setOutputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.OUTPUT));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建输入/输出参数列表
|
||||
*
|
||||
* @param thingModels 属性列表
|
||||
* @return 输入/输出参数列表
|
||||
*/
|
||||
private List<ThingModelParam> buildInputOutputParam(List<IotThingModelDO> thingModels,
|
||||
IotThingModelParamDirectionEnum direction) {
|
||||
return convertList(thingModels, thingModel ->
|
||||
BeanUtils.toBean(thingModel.getProperty(), ThingModelParam.class).setParaOrder(0) // TODO @puhui999: 先搞个默认值看看怎么个事
|
||||
.setDirection(direction.getDirection()));
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,9 @@
|
||||
package cn.iocoder.yudao.module.iot.util;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.enums.IotConstants;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_DEVICE_NOT_EXISTS;
|
||||
|
||||
// TODO @芋艿:可能要思索下,有没更好的处理方式
|
||||
// TODO @芋艿:怎么改成无状态
|
||||
@ -34,20 +30,17 @@ public class IotTdDatabaseUtils {
|
||||
* @return 产品超级表表名
|
||||
*/
|
||||
public static String getProductSuperTableName(Integer deviceType, String productKey) {
|
||||
// TODO @alwayssuper:枚举字段,不要 1、2、3;不符合预期,抛出异常
|
||||
if (deviceType == null) {
|
||||
throw exception(PRODUCT_DEVICE_NOT_EXISTS);
|
||||
}
|
||||
Assert.notNull(deviceType, "deviceType 不能为空");
|
||||
if (IotProductDeviceTypeEnum.GATEWAY_SUB.getType().equals(deviceType)) {
|
||||
return String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
|
||||
} else if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
|
||||
}
|
||||
if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
|
||||
return String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
|
||||
} else if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
|
||||
}
|
||||
if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
|
||||
return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
|
||||
}
|
||||
else{
|
||||
throw exception(PRODUCT_DEVICE_NOT_EXISTS);
|
||||
}
|
||||
throw new IllegalArgumentException("deviceType 不正确");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,7 +51,6 @@ public class IotTdDatabaseUtils {
|
||||
*
|
||||
*/
|
||||
public static String getThingModelMessageSuperTableName(String productKey) {
|
||||
// TODO @alwayssuper:是不是应该 + 拼接就好,不用 format
|
||||
return "thing_model_message_" + productKey.toLowerCase();
|
||||
}
|
||||
|
||||
@ -70,7 +62,8 @@ public class IotTdDatabaseUtils {
|
||||
* @return 物模型日志设备表名
|
||||
*/
|
||||
public static String getThingModelMessageDeviceTableName(String productKey, String deviceName) {
|
||||
return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
|
||||
return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT,
|
||||
productKey.toLowerCase(), deviceName.toLowerCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper">
|
||||
|
||||
<select id="getProductPropertySTableFieldList" resultType="cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField">
|
||||
DESCRIBE product_property_${productKey}
|
||||
</select>
|
||||
|
||||
<update id="createProductPropertySTable">
|
||||
CREATE STABLE product_property_${productKey}
|
||||
<foreach item="field" collection="fields" separator="," open="(" close=")">
|
||||
${field.field} ${field.type}
|
||||
<if test="field.length != null and field.length > 0">
|
||||
(${field.length})
|
||||
</if>
|
||||
</foreach>
|
||||
TAGS (
|
||||
device_key NCHAR(50)
|
||||
)
|
||||
</update>
|
||||
|
||||
<update id="alterProductPropertySTableAddField">
|
||||
ALTER STABLE product_property_${productKey}
|
||||
ADD COLUMN ${field.field} ${field.type}
|
||||
<if test="field.length != null and field.length > 0">
|
||||
(${field.length})
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<update id="alterProductPropertySTableModifyField">
|
||||
ALTER STABLE product_property_${productKey}
|
||||
MODIFY COLUMN ${field.field} ${field.type}
|
||||
<if test="field.length != null and field.length > 0">
|
||||
(${field.length})
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<update id="alterProductPropertySTableDropField">
|
||||
ALTER STABLE product_property_${productKey}
|
||||
DROP COLUMN ${field.field}
|
||||
</update>
|
||||
|
||||
</mapper>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper">
|
||||
<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
|
@ -5,7 +5,6 @@
|
||||
<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper">
|
||||
|
||||
<!-- 创建物模型消息日志超级表 -->
|
||||
<!-- TODO @芋艿:捉摸下字段,特别是 sys、ts 这种缩写 -->
|
||||
<update id="createSuperTable">
|
||||
CREATE STABLE ${dataBaseName}.${superTableName}(
|
||||
ts TIMESTAMP,
|
||||
|
@ -24,6 +24,7 @@
|
||||
<plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class>
|
||||
<plugin.version>0.0.1</plugin.version>
|
||||
<plugin.provider>ahh</plugin.provider>
|
||||
<plugin.description>http-plugin-0.0.1</plugin.description>
|
||||
<plugin.dependencies/>
|
||||
</properties>
|
||||
|
||||
@ -104,6 +105,7 @@
|
||||
<Plugin-Class>${plugin.class}</Plugin-Class>
|
||||
<Plugin-Version>${plugin.version}</Plugin-Version>
|
||||
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
|
||||
<Plugin-Description>${plugin.description}</Plugin-Description>
|
||||
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@ -12,12 +12,9 @@ import io.netty.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
|
||||
* <p>
|
||||
* 请求格式:
|
||||
* POST /sys/{productKey}/{deviceName}/thing/event/property/post
|
||||
* 请求体为 JSON 格式数据。
|
||||
* <p>
|
||||
* 返回结果为 JSON 格式,包含统一的 code、data、id、message、method、version 字段。
|
||||
*
|
||||
* 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
|
||||
* 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
|
||||
*/
|
||||
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
|
||||
@ -29,10 +26,9 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
|
||||
String uri = request.uri();
|
||||
|
||||
// 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
|
||||
// 使用 "/" 拆分路径
|
||||
String uri = request.uri();
|
||||
String[] parts = uri.split("/");
|
||||
|
||||
/*
|
||||
@ -52,25 +48,19 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
&& "event".equals(parts[5])
|
||||
&& "property".equals(parts[6])
|
||||
&& "post".equals(parts[7]);
|
||||
|
||||
if (!isCorrectPath) {
|
||||
// 如果路径不匹配,返回 404 错误
|
||||
writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
String productKey = parts[2];
|
||||
String deviceName = parts[3];
|
||||
|
||||
// 从请求中获取原始数据
|
||||
// 从请求中获取原始数据,尝试解析请求数据为 JSON 对象
|
||||
String requestBody = request.content().toString(CharsetUtil.UTF_8);
|
||||
|
||||
// 尝试解析请求数据为 JSON 对象
|
||||
JSONObject jsonData;
|
||||
try {
|
||||
jsonData = JSONUtil.parseObj(requestBody);
|
||||
} catch (Exception e) {
|
||||
// 数据不是合法的 JSON 格式,返回 400 错误
|
||||
JSONObject res = createResponseJson(
|
||||
400,
|
||||
new JSONObject(),
|
||||
@ -82,8 +72,6 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取请求中的 id 字段,若不存在则为 null
|
||||
String id = jsonData.getStr("id", null);
|
||||
|
||||
try {
|
||||
@ -101,7 +89,6 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
);
|
||||
writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
|
||||
} catch (Exception e) {
|
||||
// 保存数据过程中出现异常,返回 500 错误
|
||||
JSONObject errorRes = createResponseJson(
|
||||
500,
|
||||
new JSONObject(),
|
||||
@ -128,7 +115,7 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) {
|
||||
JSONObject res = new JSONObject();
|
||||
res.set("code", code);
|
||||
res.set("data", data != null ? data : new JSONObject()); // 确保 data 不为 null
|
||||
res.set("data", data != null ? data : new JSONObject());
|
||||
res.set("id", id);
|
||||
res.set("message", message);
|
||||
res.set("method", method);
|
||||
@ -137,24 +124,24 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 向客户端返回 HTTP 响应的辅助方法。
|
||||
* 向客户端返回 HTTP 响应的辅助方法
|
||||
*
|
||||
* @param ctx 通道上下文
|
||||
* @param status HTTP 响应状态码(网络层面的)
|
||||
* @param content 响应内容(JSON 字符串或其他文本)
|
||||
*/
|
||||
private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
|
||||
// 设置响应头为 JSON 类型和正确的编码
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1,
|
||||
status,
|
||||
Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)
|
||||
);
|
||||
|
||||
// 设置响应头为 JSON 类型和正确的编码
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
|
||||
|
||||
// 发送响应并在发送完成后关闭连接
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.plugin;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
@ -18,18 +18,25 @@ import java.util.concurrent.Executors;
|
||||
public class HttpPlugin extends Plugin {
|
||||
|
||||
private static final int PORT = 8092;
|
||||
private final ExecutorService executorService;
|
||||
private DeviceDataApi deviceDataApi; // 用于保存设备数据的 API
|
||||
|
||||
private ExecutorService executorService;
|
||||
private DeviceDataApi deviceDataApi;
|
||||
|
||||
public HttpPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
this.executorService = Executors.newSingleThreadExecutor(); // 创建单线程池
|
||||
// 初始化线程池
|
||||
this.executorService = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
log.info("HttpPlugin.start()");
|
||||
|
||||
// 重新初始化线程池,确保它是活跃的
|
||||
if (executorService.isShutdown() || executorService.isTerminated()) {
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
// 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例
|
||||
deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
|
||||
if (deviceDataApi == null) {
|
||||
@ -44,10 +51,13 @@ public class HttpPlugin extends Plugin {
|
||||
@Override
|
||||
public void stop() {
|
||||
log.info("HttpPlugin.stop()");
|
||||
executorService.shutdownNow(); // 停止线程池
|
||||
// 停止线程池
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
// 启动 HTTP 服务
|
||||
/**
|
||||
* 启动 HTTP 服务
|
||||
*/
|
||||
private void startHttpServer() {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
@ -56,7 +66,8 @@ public class HttpPlugin extends Plugin {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
.childHandler(new ChannelInitializer<>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel channel) {
|
||||
channel.pipeline().addLast(new HttpServerCodec());
|
||||
@ -64,6 +75,7 @@ public class HttpPlugin extends Plugin {
|
||||
// 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
|
||||
channel.pipeline().addLast(new HttpHandler(deviceDataApi));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 绑定端口并启动服务器
|
||||
@ -72,10 +84,11 @@ public class HttpPlugin extends Plugin {
|
||||
future.channel().closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("HTTP 服务启动中断", e);
|
||||
log.warn("HTTP 服务启动被中断", e);
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user