feat:【IoT 物联网】物模型数据使用 NVARCHAR 存储,并兼容 struct、array 等数据结构

This commit is contained in:
YunaiV
2025-06-29 17:09:20 +08:00
parent fd00fb2954
commit da60e649df
23 changed files with 109 additions and 93 deletions

View File

@@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyDetailRespVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyDetailRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property; package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;

View File

@@ -1,14 +1,15 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel; package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.*; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.*;
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.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import com.google.common.base.Objects;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -32,6 +33,8 @@ public class IotThingModelController {
@Resource @Resource
private IotThingModelService thingModelService; private IotThingModelService thingModelService;
@Resource
private IotProductService productService;
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建产品物模型") @Operation(summary = "创建产品物模型")
@@ -71,23 +74,21 @@ public class IotThingModelController {
@Parameter(name = "productId", description = "产品 ID", required = true, example = "1024") @Parameter(name = "productId", description = "产品 ID", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:thing-model:query')") @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
public CommonResult<IotThingModelTSLRespVO> getThingModelTsl(@RequestParam("productId") Long productId) { public CommonResult<IotThingModelTSLRespVO> getThingModelTsl(@RequestParam("productId") Long productId) {
IotThingModelTSLRespVO tslRespVO = new IotThingModelTSLRespVO(); // 1. 获得产品
// TODO @puhui999是不是要先查询产品哈原因是万一没配置物模型但是产品已经有了 IotProductDO product = productService.getProduct(productId);
// 1. 获得产品所有物模型定义 if (product == null) {
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductId(productId); return success(null);
if (CollUtil.isEmpty(thingModels)) {
return success(tslRespVO);
} }
IotThingModelTSLRespVO tslRespVO = new IotThingModelTSLRespVO()
// 2. 设置公共部分参数 .setProductId(product.getId()).setProductKey(product.getProductKey());
IotThingModelDO thingModel = thingModels.get(0); // 2. 获得物模型定义
tslRespVO.setProductId(thingModel.getProductId()).setProductKey(thingModel.getProductKey()); List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductId(productId);
tslRespVO.setProperties(convertList(filterList(thingModels, item -> tslRespVO.setProperties(convertList(filterList(thingModels, item ->
ObjUtil.equal(IotThingModelTypeEnum.PROPERTY.getType(), item.getType())), IotThingModelDO::getProperty)); Objects.equal(IotThingModelTypeEnum.PROPERTY.getType(), item.getType())), IotThingModelDO::getProperty))
tslRespVO.setServices(convertList(filterList(thingModels, item -> .setServices(convertList(filterList(thingModels, item ->
ObjUtil.equal(IotThingModelTypeEnum.SERVICE.getType(), item.getType())), IotThingModelDO::getService)); Objects.equal(IotThingModelTypeEnum.SERVICE.getType(), item.getType())), IotThingModelDO::getService))
tslRespVO.setEvents(convertList(filterList(thingModels, item -> .setEvents(convertList(filterList(thingModels, item ->
ObjUtil.equal(IotThingModelTypeEnum.EVENT.getType(), item.getType())), IotThingModelDO::getEvent)); Objects.equal(IotThingModelTypeEnum.EVENT.getType(), item.getType())), IotThingModelDO::getEvent));
return success(tslRespVO); return success(tslRespVO);
} }

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo; package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;

View File

@@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo; package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum; 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.dal.dataobject.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid; import jakarta.validation.Valid;

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo; package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -12,7 +12,7 @@ import java.util.List;
@Data @Data
public class IotThingModelTSLRespVO { public class IotThingModelTSLRespVO {
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long productId; private Long productId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor") @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor")

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.iot.convert.thingmodel; 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.dal.dataobject.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO; 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.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;

View File

@@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceEventTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceEventTypeEnum;

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelParamDirectionEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelParamDirectionEnum;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceCallTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceCallTypeEnum;

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.Valid; import jakarta.validation.Valid;

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
@@ -10,7 +10,7 @@ import lombok.EqualsAndHashCode;
/** /**
* IoT 物模型数据类型为布尔型或枚举型的 DataSpec 定义 * IoT 物模型数据类型为布尔型或枚举型的 DataSpec 定义
* *
* 数据类型取值为 bool enum * 数据类型取值为 bool enum
* *
* @author HUIHUI * @author HUIHUI
*/ */

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -7,8 +7,8 @@ import lombok.Data;
/** /**
* IoT ThingModelDataSpecs 抽象类 * IoT ThingModelDataSpecs 抽象类
* *
* 用于表示物模型数据的通用类型根据具体的 "dataType" 字段动态映射到对应的子类 * 用于表示物模型数据的通用类型根据具体的 "dataType" 字段动态映射到对应的子类
* 提供多态支持适用于不同类型的数据结构序列化和反序列化场景 * 提供多态支持适用于不同类型的数据结构序列化和反序列化场景
* *
* @author HUIHUI * @author HUIHUI
*/ */

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
@@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
/** /**
* IoT 物模型数据类型为时间型或文本型的 DataSpec 定义 * IoT 物模型数据类型为时间型或文本型的 DataSpec 定义
* *
* 数据类型取值为 date text * 数据类型取值为 date text
* *
* @author HUIHUI * @author HUIHUI
*/ */
@@ -18,13 +18,14 @@ import lombok.EqualsAndHashCode;
public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs { public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
/** /**
* 数据长度单位为字节取值不能超过 2048 * 数据长度单位为字节取值不能超过 2048
* dataType text 需传入该参数 *
* dataType text 需传入该参数
*/ */
@Max(value = 2048, message = "数据长度不能超过 2048") @Max(value = 2048, message = "数据长度不能超过 2048")
private Integer length; private Integer length;
/** /**
* 默认值可选参数用于存储默认值 * 默认值可选参数用于存储默认值
*/ */
private String defaultValue; private String defaultValue;

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
@@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
/** /**
* IoT 物模型数据类型为数值的 DataSpec 定义 * IoT 物模型数据类型为数值的 DataSpec 定义
* *
* 数据类型取值为 intfloat double * 数据类型取值为 intfloat double
* *
* @author HUIHUI * @author HUIHUI
*/ */
@@ -19,37 +19,37 @@ import lombok.EqualsAndHashCode;
public class ThingModelNumericDataSpec extends ThingModelDataSpecs { public class ThingModelNumericDataSpec extends ThingModelDataSpecs {
/** /**
* 最大值需转为字符串类型值必须与 dataType 类型一致 * 最大值需转为字符串类型值必须与 dataType 类型一致
*/ */
@NotEmpty(message = "最大值不能为空") @NotEmpty(message = "最大值不能为空")
@Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最大值必须为数值类型") @Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最大值必须为数值类型")
private String max; private String max;
/** /**
* 最小值需转为字符串类型值必须与 dataType 类型一致 * 最小值需转为字符串类型值必须与 dataType 类型一致
*/ */
@NotEmpty(message = "最小值不能为空") @NotEmpty(message = "最小值不能为空")
@Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最小值必须为数值类型") @Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最小值必须为数值类型")
private String min; private String min;
/** /**
* 步长需转为字符串类型值必须与 dataType 类型一致 * 步长需转为字符串类型值必须与 dataType 类型一致
*/ */
@NotEmpty(message = "步长不能为空") @NotEmpty(message = "步长不能为空")
@Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "步长必须为数值类型") @Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "步长必须为数值类型")
private String step; private String step;
/** /**
* 精度 dataType float double 时可选传入 * 精度 dataType float double 时可选传入
*/ */
private String precise; private String precise;
/** /**
* 默认值可传入用于存储的默认值 * 默认值可传入用于存储的默认值
*/ */
private String defaultValue; private String defaultValue;
/** /**
* 单位的符号 * 单位的符号
*/ */
private String unit; private String unit;
/** /**
* 单位的名称 * 单位的名称
*/ */
private String unitName; private String unitName;

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType; package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;

View File

@@ -23,8 +23,14 @@ public class TDengineTableField {
public static final String TYPE_DOUBLE = "DOUBLE"; public static final String TYPE_DOUBLE = "DOUBLE";
public static final String TYPE_BOOL = "BOOL"; public static final String TYPE_BOOL = "BOOL";
public static final String TYPE_NCHAR = "NCHAR"; public static final String TYPE_NCHAR = "NCHAR";
public static final String TYPE_VARCHAR = "VARCHAR";
public static final String TYPE_TIMESTAMP = "TIMESTAMP"; public static final String TYPE_TIMESTAMP = "TIMESTAMP";
/**
* 字段长度 - VARCHAR 默认长度
*/
public static final int LENGTH_VARCHAR = 1024;
/** /**
* 注释 - TAG 字段 * 注释 - TAG 字段
*/ */

View File

@@ -4,14 +4,16 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO; import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO;
import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO; import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO;
import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceServerIdRedisDAO; import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceServerIdRedisDAO;
@@ -43,17 +45,19 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
/** /**
* 物模型的数据类型,与 TDengine 数据类型的映射关系 * 物模型的数据类型,与 TDengine 数据类型的映射关系
*
* @see <a href="https://docs.taosdata.com/reference/taos-sql/data-type/">TDEngine 数据类型</a>
*/ */
private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder() private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
.put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT) .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
.put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT) .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
.put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE) .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
.put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT)
.put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT)
.put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR) .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_VARCHAR)
.put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP) .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
.put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!! .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_VARCHAR)
.put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!! .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_VARCHAR)
.build(); .build();
@Resource @Resource
@@ -109,8 +113,12 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
TDengineTableField field = new TDengineTableField( TDengineTableField field = new TDengineTableField(
StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写 StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
TYPE_MAPPING.get(thingModel.getProperty().getDataType())); TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
if (thingModel.getProperty().getDataType().equals(IotDataSpecsDataTypeEnum.TEXT.getDataType())) { String dataType = thingModel.getProperty().getDataType();
if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength()); field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
} else if (ObjectUtils.equalsAny(dataType, IotDataSpecsDataTypeEnum.STRUCT.getDataType(),
IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
field.setLength(TDengineTableField.LENGTH_VARCHAR);
} }
return field; return field;
}); });
@@ -118,7 +126,6 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
@Override @Override
public void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message) { public void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message) {
// TODO @芋艿:这里要改下协议!
if (!(message.getParams() instanceof Map)) { if (!(message.getParams() instanceof Map)) {
log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message); log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
return; return;
@@ -129,11 +136,18 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId()); List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId());
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
((Map<?, ?>) message.getParams()).forEach((key, value) -> { ((Map<?, ?>) message.getParams()).forEach((key, value) -> {
if (CollUtil.findOne(thingModels, thingModel -> thingModel.getIdentifier().equals(key)) == null) { IotThingModelDO thingModel = CollUtil.findOne(thingModels, o -> o.getIdentifier().equals(key));
if (thingModel == null || thingModel.getProperty() == null) {
log.error("[saveDeviceProperty][消息({}) 的属性({}) 不存在]", message, key); log.error("[saveDeviceProperty][消息({}) 的属性({}) 不存在]", message, key);
return; return;
} }
properties.put((String) key, value); if (ObjectUtils.equalsAny(thingModel.getProperty().getDataType(),
IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
// 特殊STRUCT 和 ARRAY 类型,在 TDengine 里,有没对应数据类型,只能通过 JSON 来存储
properties.put((String) key, JsonUtils.toJsonString(value));
} else {
properties.put((String) key, value);
}
}); });
if (CollUtil.isEmpty(properties)) { if (CollUtil.isEmpty(properties)) {
log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message); log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message);
@@ -141,8 +155,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
} }
// 2.1 保存设备属性【数据】 // 2.1 保存设备属性【数据】
devicePropertyMapper.insert(device, properties, devicePropertyMapper.insert(device, properties, LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
// 2.2 保存设备属性【日志】 // 2.2 保存设备属性【日志】
Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry -> Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry ->

View File

@@ -143,7 +143,7 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
productId -> new HashSet<>()).add(config.getIdentifier()); productId -> new HashSet<>()).add(config.getIdentifier());
} }
for (Map.Entry<Long, Set<String>> entry : productIdIdentifiers.entrySet()) { for (Map.Entry<Long, Set<String>> entry : productIdIdentifiers.entrySet()) {
thingModelService.validateThingModelsExist(entry.getKey(), entry.getValue()); thingModelService.validateThingModelListExists(entry.getKey(), entry.getValue());
} }
} }

View File

@@ -106,6 +106,6 @@ public interface IotThingModelService {
* @param productId 产品编号 * @param productId 产品编号
* @param identifiers 标识符集合 * @param identifiers 标识符集合
*/ */
void validateThingModelsExist(Long productId, Set<String> identifiers); void validateThingModelListExists(Long productId, Set<String> identifiers);
} }

View File

@@ -66,7 +66,7 @@ public class IotThingModelServiceImpl implements IotThingModelService {
thingModelMapper.insert(thingModel); thingModelMapper.insert(thingModel);
// 3. 删除缓存 // 3. 删除缓存
deleteThingModelListCache(createReqVO.getProductKey()); deleteThingModelListCache(createReqVO.getProductId());
return thingModel.getId(); return thingModel.getId();
} }
@@ -85,7 +85,7 @@ public class IotThingModelServiceImpl implements IotThingModelService {
thingModelMapper.updateById(thingModel); thingModelMapper.updateById(thingModel);
// 3. 删除缓存 // 3. 删除缓存
deleteThingModelListCache(updateReqVO.getProductKey()); deleteThingModelListCache(updateReqVO.getProductId());
} }
@Override @Override
@@ -103,7 +103,7 @@ public class IotThingModelServiceImpl implements IotThingModelService {
thingModelMapper.deleteById(id); thingModelMapper.deleteById(id);
// 3. 删除缓存 // 3. 删除缓存
deleteThingModelListCache(thingModel.getProductKey()); deleteThingModelListCache(thingModel.getProductId());
} }
@Override @Override
@@ -128,7 +128,7 @@ public class IotThingModelServiceImpl implements IotThingModelService {
@Override @Override
@Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productId") @Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productId")
@TenantIgnore // 忽略租户信息,跨租户 productKey 是唯一的 @TenantIgnore // 忽略租户信息
public List<IotThingModelDO> getThingModelListByProductIdFromCache(Long productId) { public List<IotThingModelDO> getThingModelListByProductIdFromCache(Long productId) {
return thingModelMapper.selectListByProductId(productId); return thingModelMapper.selectListByProductId(productId);
} }
@@ -144,7 +144,7 @@ public class IotThingModelServiceImpl implements IotThingModelService {
} }
@Override @Override
public void validateThingModelsExist(Long productId, Set<String> identifiers) { public void validateThingModelListExists(Long productId, Set<String> identifiers) {
if (CollUtil.isEmpty(identifiers)) { if (CollUtil.isEmpty(identifiers)) {
return; return;
} }
@@ -158,11 +158,6 @@ public class IotThingModelServiceImpl implements IotThingModelService {
} }
} }
/**
* 校验功能是否存在
*
* @param id 功能编号
*/
private void validateProductThingModelMapperExists(Long id) { private void validateProductThingModelMapperExists(Long id) {
if (thingModelMapper.selectById(id) == null) { if (thingModelMapper.selectById(id) == null) {
throw exception(THING_MODEL_NOT_EXISTS); throw exception(THING_MODEL_NOT_EXISTS);
@@ -170,13 +165,12 @@ public class IotThingModelServiceImpl implements IotThingModelService {
} }
private void validateIdentifierUnique(Long id, Long productId, String identifier) { private void validateIdentifierUnique(Long id, Long productId, String identifier) {
// 1.0 情况一:创建时校验 // 1. 情况一:创建时校验
if (id == null) { if (id == null) {
// 1.1 系统保留字段,不能用于标识符定义 // 1.1 系统保留字段,不能用于标识符定义
if (StrUtil.equalsAny(identifier, "set", "get", "post", "property", "event", "time", "value")) { if (StrUtil.equalsAny(identifier, "set", "get", "post", "property", "event", "time", "value")) {
throw exception(THING_MODEL_IDENTIFIER_INVALID); throw exception(THING_MODEL_IDENTIFIER_INVALID);
} }
// 1.2 校验唯一 // 1.2 校验唯一
IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier); IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
if (thingModel != null) { if (thingModel != null) {
@@ -185,7 +179,7 @@ public class IotThingModelServiceImpl implements IotThingModelService {
return; return;
} }
// 2.0 情况二:更新时校验 // 2. 情况二:更新时校验
IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier); IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) { if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
throw exception(THING_MODEL_IDENTIFIER_EXISTS); throw exception(THING_MODEL_IDENTIFIER_EXISTS);
@@ -206,13 +200,14 @@ public class IotThingModelServiceImpl implements IotThingModelService {
} }
} }
private void deleteThingModelListCache(String productKey) { private void deleteThingModelListCache(Long productId) {
// 保证 Spring AOP 触发 // 保证 Spring AOP 触发
getSelf().deleteThingModelListCache0(productKey); getSelf().deleteThingModelListCache0(productId);
} }
@CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey") @CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productId")
public void deleteThingModelListCache0(String productKey) { @TenantIgnore // 忽略租户信息
public void deleteThingModelListCache0(Long productId) {
} }
private IotThingModelServiceImpl getSelf() { private IotThingModelServiceImpl getSelf() {