| | |
| | | package cn.iocoder.yudao.module.iot.service.thingmodel; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.convert.Convert; |
| | | import cn.hutool.core.date.DatePattern; |
| | | import cn.hutool.core.date.LocalDateTimeUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.extra.spring.SpringUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; |
| | | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| | | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelListReqVO; |
| | | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO; |
| | |
| | | import cn.iocoder.yudao.module.iot.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.dataobject.thingmodel.model.dataType.ThingModelBoolOrEnumDataSpecs; |
| | | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs; |
| | | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; |
| | | import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper; |
| | | import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; |
| | | import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; |
| | | import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; |
| | | import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField; |
| | | import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService; |
| | | import cn.iocoder.yudao.module.iot.service.product.IotProductService; |
| | | import jakarta.annotation.Resource; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.time.Instant; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.OffsetDateTime; |
| | | import java.time.ZonedDateTime; |
| | | import java.util.Collection; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Object convertThingModelPropertyValue(IotThingModelDO thingModel, Object value) { |
| | | if (thingModel == null || thingModel.getProperty() == null || value == null) { |
| | | return null; |
| | | } |
| | | String dataType = thingModel.getProperty().getDataType(); |
| | | if (ObjectUtils.equalsAny(dataType, |
| | | IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) { |
| | | // 特殊:STRUCT 和 ARRAY 类型,在 TDengine 里,没有对应数据类型,只能通过 JSON 来存储 |
| | | return convertToVarchar(JsonUtils.toJsonString(value), TDengineTableField.LENGTH_VARCHAR); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.INT.getDataType().equals(dataType)) { |
| | | return convertToInt(value); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.FLOAT.getDataType().equals(dataType)) { |
| | | return convertToFloat(value); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.DOUBLE.getDataType().equals(dataType)) { |
| | | return convertToDouble(value); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.ENUM.getDataType().equals(dataType)) { |
| | | return convertEnumToTinyInt(thingModel, value); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.BOOL.getDataType().equals(dataType)) { |
| | | return convertBoolToTinyInt(value); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.TEXT.getDataType().equals(dataType)) { |
| | | return convertToVarchar(Convert.toStr(value), getTextLength(thingModel)); |
| | | } |
| | | if (IotDataSpecsDataTypeEnum.DATE.getDataType().equals(dataType)) { |
| | | return convertToTimestamp(value); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer getTextLength(IotThingModelDO thingModel) { |
| | | ThingModelDataSpecs dataSpecs = thingModel.getProperty().getDataSpecs(); |
| | | if (!(dataSpecs instanceof ThingModelDateOrTextDataSpecs)) { |
| | | return null; |
| | | } |
| | | return ((ThingModelDateOrTextDataSpecs) dataSpecs).getLength(); |
| | | } |
| | | |
| | | private String convertToVarchar(String value, Integer length) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | if (length != null && value.getBytes(StandardCharsets.UTF_8).length > length) { |
| | | return null; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private Integer convertToInt(Object value) { |
| | | BigDecimal decimal = convertToBigDecimal(value); |
| | | if (decimal == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return decimal.intValueExact(); |
| | | } catch (ArithmeticException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Float convertToFloat(Object value) { |
| | | BigDecimal decimal = convertToBigDecimal(value); |
| | | if (decimal == null) { |
| | | return null; |
| | | } |
| | | float result = decimal.floatValue(); |
| | | return Float.isFinite(result) ? result : null; |
| | | } |
| | | |
| | | private Double convertToDouble(Object value) { |
| | | BigDecimal decimal = convertToBigDecimal(value); |
| | | if (decimal == null) { |
| | | return null; |
| | | } |
| | | double result = decimal.doubleValue(); |
| | | return Double.isFinite(result) ? result : null; |
| | | } |
| | | |
| | | private BigDecimal convertToBigDecimal(Object value) { |
| | | if (value instanceof Boolean) { |
| | | return null; |
| | | } |
| | | String text = Convert.toStr(value); |
| | | if (StrUtil.isBlank(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return new BigDecimal(text); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Byte convertEnumToTinyInt(IotThingModelDO thingModel, Object value) { |
| | | Integer intValue = convertToInt(value); |
| | | if (intValue == null && value instanceof CharSequence) { |
| | | intValue = getEnumValueByName(thingModel, value.toString()); |
| | | } |
| | | if (intValue == null || !isTinyInt(intValue)) { |
| | | return null; |
| | | } |
| | | if (CollUtil.isNotEmpty(thingModel.getProperty().getDataSpecsList()) |
| | | && getEnumDataSpecsByValue(thingModel, intValue) == null) { |
| | | return null; |
| | | } |
| | | return intValue.byteValue(); |
| | | } |
| | | |
| | | private Integer getEnumValueByName(IotThingModelDO thingModel, String name) { |
| | | ThingModelBoolOrEnumDataSpecs dataSpecs = getEnumDataSpecsByName(thingModel, name); |
| | | return dataSpecs != null ? dataSpecs.getValue() : null; |
| | | } |
| | | |
| | | private ThingModelBoolOrEnumDataSpecs getEnumDataSpecsByName(IotThingModelDO thingModel, String name) { |
| | | if (CollUtil.isEmpty(thingModel.getProperty().getDataSpecsList())) { |
| | | return null; |
| | | } |
| | | for (ThingModelDataSpecs dataSpecs : thingModel.getProperty().getDataSpecsList()) { |
| | | if (!(dataSpecs instanceof ThingModelBoolOrEnumDataSpecs)) { |
| | | continue; |
| | | } |
| | | ThingModelBoolOrEnumDataSpecs enumDataSpecs = (ThingModelBoolOrEnumDataSpecs) dataSpecs; |
| | | if (StrUtil.equals(enumDataSpecs.getName(), name)) { |
| | | return enumDataSpecs; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private ThingModelBoolOrEnumDataSpecs getEnumDataSpecsByValue(IotThingModelDO thingModel, Integer value) { |
| | | if (CollUtil.isEmpty(thingModel.getProperty().getDataSpecsList())) { |
| | | return null; |
| | | } |
| | | for (ThingModelDataSpecs dataSpecs : thingModel.getProperty().getDataSpecsList()) { |
| | | if (!(dataSpecs instanceof ThingModelBoolOrEnumDataSpecs)) { |
| | | continue; |
| | | } |
| | | ThingModelBoolOrEnumDataSpecs enumDataSpecs = (ThingModelBoolOrEnumDataSpecs) dataSpecs; |
| | | if (Objects.equals(enumDataSpecs.getValue(), value)) { |
| | | return enumDataSpecs; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Byte convertBoolToTinyInt(Object value) { |
| | | if (value instanceof Boolean) { |
| | | return (Boolean) value ? (byte) 1 : (byte) 0; |
| | | } |
| | | if (value instanceof CharSequence) { |
| | | String text = StrUtil.trim(value.toString()); |
| | | if (StrUtil.equalsIgnoreCase(text, "true")) { |
| | | return (byte) 1; |
| | | } |
| | | if (StrUtil.equalsIgnoreCase(text, "false")) { |
| | | return (byte) 0; |
| | | } |
| | | } |
| | | Integer intValue = convertToInt(value); |
| | | if (intValue == null || (intValue != 0 && intValue != 1)) { |
| | | return null; |
| | | } |
| | | return intValue.byteValue(); |
| | | } |
| | | |
| | | private boolean isTinyInt(Integer value) { |
| | | return value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE; |
| | | } |
| | | |
| | | private Long convertToTimestamp(Object value) { |
| | | if (value instanceof LocalDateTime) { |
| | | return LocalDateTimeUtil.toEpochMilli((LocalDateTime) value); |
| | | } |
| | | if (value instanceof LocalDate) { |
| | | return LocalDateTimeUtil.toEpochMilli(((LocalDate) value).atStartOfDay()); |
| | | } |
| | | if (value instanceof Instant) { |
| | | return ((Instant) value).toEpochMilli(); |
| | | } |
| | | if (value instanceof OffsetDateTime) { |
| | | return ((OffsetDateTime) value).toInstant().toEpochMilli(); |
| | | } |
| | | if (value instanceof ZonedDateTime) { |
| | | return ((ZonedDateTime) value).toInstant().toEpochMilli(); |
| | | } |
| | | if (value instanceof Date) { |
| | | return ((Date) value).getTime(); |
| | | } |
| | | Long timestamp = convertToLong(value); |
| | | if (timestamp != null) { |
| | | return timestamp; |
| | | } |
| | | if (!(value instanceof CharSequence)) { |
| | | return null; |
| | | } |
| | | String text = StrUtil.trim(value.toString()); |
| | | if (StrUtil.isBlank(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return OffsetDateTime.parse(text).toInstant().toEpochMilli(); |
| | | } catch (Exception ignored) { |
| | | // 尝试本地时间格式,例如 yyyy-MM-dd HH:mm:ss |
| | | } |
| | | try { |
| | | return LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.parse(text, DatePattern.NORM_DATETIME_PATTERN)); |
| | | } catch (Exception ignored) { |
| | | // 尝试其它 Hutool 支持的本地时间格式 |
| | | } |
| | | try { |
| | | return LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtils.parse(text)); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Long convertToLong(Object value) { |
| | | BigDecimal decimal = convertToBigDecimal(value); |
| | | if (decimal == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return decimal.longValueExact(); |
| | | } catch (ArithmeticException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private void validateProductThingModelMapperExists(Long id) { |
| | | if (thingModelMapper.selectById(id) == null) { |
| | | throw exception(THING_MODEL_NOT_EXISTS); |