Compare commits
18 Commits
v2.3.0(jdk
...
feature/io
Author | SHA1 | Date | |
---|---|---|---|
![]() |
db9c485285 | ||
![]() |
9841c869a2 | ||
![]() |
a8c87d168a | ||
![]() |
9b9fd30c90 | ||
![]() |
3a2c691af0 | ||
![]() |
ce919d12d1 | ||
![]() |
89fb71e857 | ||
![]() |
9b30d5d355 | ||
![]() |
e3dcea9cb3 | ||
![]() |
d7b8cf547f | ||
![]() |
624f5283b3 | ||
![]() |
3dafd31da6 | ||
![]() |
8c84ac9d8a | ||
![]() |
88088c7987 | ||
![]() |
7b5aa23d5c | ||
![]() |
ea8dd67e9e | ||
![]() |
61a0c05279 | ||
![]() |
05c5482715 |
@@ -33,6 +33,7 @@
|
||||
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
|
||||
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
|
||||
<opengauss.jdbc.version>5.0.2</opengauss.jdbc.version>
|
||||
<taos.version>3.3.3</taos.version>
|
||||
<!-- 消息队列 -->
|
||||
<rocketmq-spring.version>2.3.0</rocketmq-spring.version>
|
||||
<!-- 服务保障相关 -->
|
||||
@@ -253,6 +254,12 @@
|
||||
<version>${kingbase.jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
<version>${taos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 定时任务相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
|
@@ -63,6 +63,10 @@
|
||||
<artifactId>opengauss-jdbc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
|
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.yudao.module.iot.enums;
|
||||
|
||||
/**
|
||||
* IoT 字典类型的枚举类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DictTypeConstants {
|
||||
|
||||
public static final String PRODUCT_STATUS = "iot_product_status";
|
||||
public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
|
||||
public static final String NET_TYPE = "iot_net_type";
|
||||
public static final String PROTOCOL_TYPE = "iot_protocol_type";
|
||||
public static final String DATA_FORMAT = "iot_data_format";
|
||||
public static final String VALIDATE_TYPE = "iot_validate_type";
|
||||
}
|
@@ -9,19 +9,20 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== IoT 产品相关 1-050-001-000 ============
|
||||
// ========== 产品相关 1-050-001-000 ============
|
||||
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
|
||||
ErrorCode PRODUCT_IDENTIFICATION_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
|
||||
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_FUNCTION = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
|
||||
|
||||
// ========== IoT 产品物模型 1-050-002-000 ============
|
||||
// ========== 产品物模型 1-050-002-000 ============
|
||||
ErrorCode THINK_MODEL_FUNCTION_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
|
||||
ErrorCode THINK_MODEL_FUNCTION_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
|
||||
ErrorCode THINK_MODEL_FUNCTION_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
|
||||
ErrorCode THINK_MODEL_FUNCTION_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
|
||||
ErrorCode THINK_MODEL_FUNCTION_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
|
||||
|
||||
// ========== IoT 设备 1-050-003-000 ============
|
||||
// ========== 设备 1-050-003-000 ============
|
||||
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
|
||||
ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
|
||||
ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
|
||||
@@ -29,4 +30,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode DEVICE_PRODUCT_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_004, "产品不能修改");
|
||||
ErrorCode DEVICE_INVALID_DEVICE_STATUS = new ErrorCode(1_050_003_005, "无效的设备状态");
|
||||
|
||||
// ========== 产品分类 1-050-004-000 ==========
|
||||
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.iot.enums;
|
||||
|
||||
/**
|
||||
* Iot 常量
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotConstants {
|
||||
|
||||
/**
|
||||
* 获取设备表名
|
||||
* <p>
|
||||
* 格式为 device_{productKey}_{deviceName}
|
||||
*/
|
||||
String DEVICE_TABLE_NAME_FORMAT = "device_%s_%s";
|
||||
|
||||
/**
|
||||
* 获取产品属性超级表名 - 网关子设备
|
||||
* <p>
|
||||
* 格式为 gateway_sub_{productKey}
|
||||
*/
|
||||
String GATEWAY_SUB_STABLE_NAME_FORMAT = "gateway_sub_%s";
|
||||
|
||||
/**
|
||||
* 获取产品属性超级表名 - 网关
|
||||
* <p>
|
||||
* 格式为 gateway_{productKey}
|
||||
*/
|
||||
String GATEWAY_STABLE_NAME_FORMAT = "gateway_%s";
|
||||
|
||||
/**
|
||||
* 获取产品属性超级表名 - 设备
|
||||
* <p>
|
||||
* 格式为 device_{productKey}
|
||||
*/
|
||||
String DEVICE_STABLE_NAME_FORMAT = "device_%s";
|
||||
|
||||
}
|
@@ -30,6 +30,15 @@ public enum IotProductFunctionTypeEnum implements IntArrayValuable {
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
public static IotProductFunctionTypeEnum valueOfType(Integer type) {
|
||||
for (IotProductFunctionTypeEnum value : values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
|
@@ -18,12 +18,12 @@ public enum IotProductStatusEnum implements IntArrayValuable {
|
||||
UNPUBLISHED(0, "开发中"),
|
||||
PUBLISHED(1, "已发布");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductStatusEnum::getType).toArray();
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductStatusEnum::getStatus).toArray();
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
private final Integer status;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
|
@@ -25,6 +25,11 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
@@ -42,6 +47,11 @@
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
|
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.controller.admin.device;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
@@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
|
||||
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 io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 设备数据")
|
||||
@RestController
|
||||
@RequestMapping("/iot/device/data")
|
||||
@Validated
|
||||
public class IotDeviceDataController {
|
||||
|
||||
@Resource
|
||||
private IotDeviceDataService deviceDataService;
|
||||
|
||||
// TODO @浩浩:这里的 /latest-list,包括方法名。
|
||||
@GetMapping("/latest")
|
||||
@Operation(summary = "获取设备属性最新数据")
|
||||
public CommonResult<List<IotDeviceDataRespVO>> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
|
||||
List<IotDeviceDataDO> list = deviceDataService.getLatestDeviceProperties(deviceDataReqVO);
|
||||
return success(BeanUtils.toBean(list, IotDeviceDataRespVO.class));
|
||||
}
|
||||
|
||||
// TODO @浩浩:这里的 /history-page 包括方法名。
|
||||
@GetMapping("/history")
|
||||
@Operation(summary = "获取设备属性历史数据")
|
||||
public CommonResult<PageResult<IotTimeDataRespVO>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
|
||||
PageResult<Map<String, Object>> list = deviceDataService.getHistoryDeviceProperties(deviceDataReqVO);
|
||||
return success(BeanUtils.toBean(list, IotTimeDataRespVO.class));
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
@@ -10,7 +10,6 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
@@ -1,11 +1,10 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备 Response VO")
|
@@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
@@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
|
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备数据 Request VO")
|
||||
@Data
|
||||
public class IotDeviceDataPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String identifier;
|
||||
|
||||
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Size(min = 2, max = 2, message = "请选择时间范围")
|
||||
private LocalDateTime[] times;
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备数据 Response VO")
|
||||
@Data
|
||||
public class IotDeviceDataRespVO {
|
||||
|
||||
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "物模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
|
||||
private Long thinkModelFunctionId;
|
||||
|
||||
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
|
||||
private String deviceName;
|
||||
|
||||
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String identifier;
|
||||
|
||||
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String dataType;
|
||||
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "最新值", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String value;
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotTimeDataRespVO {
|
||||
|
||||
/**
|
||||
* 时间
|
||||
*/
|
||||
private long time;
|
||||
|
||||
/**
|
||||
* 数据值
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.category.IotProductCategoryPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.category.IotProductCategoryRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.category.IotProductCategorySaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductCategoryDO;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;
|
||||
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;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 产品分类")
|
||||
@RestController
|
||||
@RequestMapping("/iot/product-category")
|
||||
@Validated
|
||||
public class IotProductCategoryController {
|
||||
|
||||
@Resource
|
||||
private IotProductCategoryService productCategoryService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建产品分类")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-category:create')")
|
||||
public CommonResult<Long> createProductCategory(@Valid @RequestBody IotProductCategorySaveReqVO createReqVO) {
|
||||
return success(productCategoryService.createProductCategory(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新产品分类")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-category:update')")
|
||||
public CommonResult<Boolean> updateProductCategory(@Valid @RequestBody IotProductCategorySaveReqVO updateReqVO) {
|
||||
productCategoryService.updateProductCategory(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除产品分类")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-category:delete')")
|
||||
public CommonResult<Boolean> deleteProductCategory(@RequestParam("id") Long id) {
|
||||
productCategoryService.deleteProductCategory(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得产品分类")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-category:query')")
|
||||
public CommonResult<IotProductCategoryRespVO> getProductCategory(@RequestParam("id") Long id) {
|
||||
IotProductCategoryDO productCategory = productCategoryService.getProductCategory(id);
|
||||
return success(BeanUtils.toBean(productCategory, IotProductCategoryRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得产品分类分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-category:query')")
|
||||
public CommonResult<PageResult<IotProductCategoryRespVO>> getProductCategoryPage(@Valid IotProductCategoryPageReqVO pageReqVO) {
|
||||
PageResult<IotProductCategoryDO> pageResult = productCategoryService.getProductCategoryPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotProductCategoryRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得所有产品分类列表")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product-category:query')")
|
||||
public CommonResult<List<IotProductCategoryRespVO>> getSimpleProductCategoryList() {
|
||||
List<IotProductCategoryDO> list = productCategoryService.getProductCategoryListByStatus(
|
||||
CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, category ->
|
||||
new IotProductCategoryRespVO().setId(category.getId()).setName(category.getName())));
|
||||
}
|
||||
|
||||
}
|
@@ -1,26 +1,36 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSimpleRespVO;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductCategoryDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
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.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 产品")
|
||||
@RestController
|
||||
@@ -30,6 +40,8 @@ public class IotProductController {
|
||||
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
@Resource
|
||||
private IotProductCategoryService categoryService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建产品")
|
||||
@@ -72,7 +84,13 @@ public class IotProductController {
|
||||
@PreAuthorize("@ss.hasPermission('iot:product:query')")
|
||||
public CommonResult<IotProductRespVO> getProduct(@RequestParam("id") Long id) {
|
||||
IotProductDO product = productService.getProduct(id);
|
||||
return success(BeanUtils.toBean(product, IotProductRespVO.class));
|
||||
// 拼接数据
|
||||
IotProductCategoryDO category = categoryService.getProductCategory(product.getCategoryId());
|
||||
return success(BeanUtils.toBean(product, IotProductRespVO.class, bean -> {
|
||||
if (category != null) {
|
||||
bean.setCategoryName(category.getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@@ -80,16 +98,35 @@ public class IotProductController {
|
||||
@PreAuthorize("@ss.hasPermission('iot:product:query')")
|
||||
public CommonResult<PageResult<IotProductRespVO>> getProductPage(@Valid IotProductPageReqVO pageReqVO) {
|
||||
PageResult<IotProductDO> pageResult = productService.getProductPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotProductRespVO.class));
|
||||
// 拼接数据
|
||||
Map<Long, IotProductCategoryDO> categoryMap = categoryService.getProductCategoryMap(
|
||||
convertList(pageResult.getList(), IotProductDO::getCategoryId));
|
||||
return success(BeanUtils.toBean(pageResult, IotProductRespVO.class, bean -> {
|
||||
MapUtils.findAndThen(categoryMap, bean.getCategoryId(),
|
||||
category -> bean.setCategoryName(category.getName()));
|
||||
}));
|
||||
}
|
||||
|
||||
// TODO @haohao:改成 simple-list 哈
|
||||
@GetMapping("/list-all-simple")
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出产品 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportProductExcel(@Valid IotProductPageReqVO exportReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
CommonResult<PageResult<IotProductRespVO>> result = getProductPage(exportReqVO);
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "产品.xls", "数据", IotProductRespVO.class,
|
||||
result.getData().getList());
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得所有产品列表")
|
||||
@PreAuthorize("@ss.hasPermission('iot:product:query')")
|
||||
public CommonResult<List<IotProductSimpleRespVO>> listAllSimpleProducts() {
|
||||
public CommonResult<List<IotProductRespVO>> getSimpleProductList() {
|
||||
List<IotProductDO> list = productService.getProductList();
|
||||
return success(BeanUtils.toBean(list, IotProductSimpleRespVO.class));
|
||||
return success(convertList(list, product -> // 只返回 id、name 字段
|
||||
new IotProductRespVO().setId(product.getId()).setName(product.getName())));
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品 Response VO")
|
||||
@Data
|
||||
public class IotProductSimpleRespVO {
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26087")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
private String name;
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.category;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品分类分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IotProductCategoryPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "分类名字", example = "王五")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.category;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品分类 Response VO")
|
||||
@Data
|
||||
public class IotProductCategoryRespVO {
|
||||
|
||||
@Schema(description = "分类 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25284")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "分类名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "分类排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@DictFormat(DictTypeConstants.COMMON_STATUS)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "分类描述", example = "随便")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.category;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品分类新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotProductCategorySaveReqVO {
|
||||
|
||||
@Schema(description = "分类 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25284")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "分类名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
|
||||
@NotEmpty(message = "分类名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "分类排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "分类状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "分类描述", example = "随便")
|
||||
private String description;
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
@@ -1,5 +1,8 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
|
||||
import cn.iocoder.yudao.module.iot.enums.DictTypeConstants;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -20,48 +23,65 @@ public class IotProductRespVO {
|
||||
@ExcelProperty("产品名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("产品标识")
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("产品分类")
|
||||
private String categoryName;
|
||||
|
||||
@Schema(description = "产品图标", example = "https://iocoder.cn/1.svg")
|
||||
@ExcelProperty("产品图标")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "产品图标", example = "https://iocoder.cn/1.png")
|
||||
@ExcelProperty("产品图标")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description = "产品描述", example = "你猜")
|
||||
@ExcelProperty("产品描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "产品状态", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.PRODUCT_STATUS)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@ExcelProperty(value = "设备类型", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.PRODUCT_DEVICE_TYPE)
|
||||
private Integer deviceType;
|
||||
|
||||
@Schema(description = "联网方式", example = "2")
|
||||
@ExcelProperty(value = "联网方式", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.NET_TYPE)
|
||||
private Integer netType;
|
||||
|
||||
@Schema(description = "接入网关协议", example = "2")
|
||||
@ExcelProperty("接入网关协议")
|
||||
@ExcelProperty(value = "接入网关协议", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.PROTOCOL_TYPE)
|
||||
private Integer protocolType;
|
||||
|
||||
@Schema(description = "协议编号(脚本解析 id)", requiredMode = Schema.RequiredMode.REQUIRED, example = "13177")
|
||||
@ExcelProperty("协议编号(脚本解析 id)")
|
||||
private Long protocolId;
|
||||
|
||||
@Schema(description = "产品所属品类标识符", example = "14237")
|
||||
@ExcelProperty("产品所属品类标识符")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "产品描述", example = "你猜")
|
||||
@ExcelProperty("产品描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("数据校验级别")
|
||||
private Integer validateType;
|
||||
|
||||
@Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("产品状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@ExcelProperty("设备类型")
|
||||
private Integer deviceType;
|
||||
|
||||
@Schema(description = "联网方式", example = "2")
|
||||
@ExcelProperty("联网方式")
|
||||
private Integer netType;
|
||||
|
||||
@Schema(description = "数据格式")
|
||||
@ExcelProperty("数据格式")
|
||||
@ExcelProperty(value = "数据格式", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.DATA_FORMAT)
|
||||
private Integer dataFormat;
|
||||
|
||||
@Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "数据校验级别", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.VALIDATE_TYPE)
|
||||
private Integer validateType;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.*;
|
||||
@@ -14,13 +14,26 @@ public class IotProductSaveReqVO {
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.AUTO, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "产品Key", requiredMode = Schema.RequiredMode.AUTO, example = "12345abc")
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温湿度")
|
||||
@NotEmpty(message = "产品名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "产品 Key", requiredMode = Schema.RequiredMode.AUTO, example = "12345abc")
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "产品分类编号不能为空")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "产品图标", example = "https://iocoder.cn/1.svg")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "产品图标", example = "https://iocoder.cn/1.png")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description = "产品描述", example = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@InEnum(value = IotProductDeviceTypeEnum.class, message = "设备类型必须是 {value}")
|
||||
@NotNull(message = "设备类型不能为空")
|
||||
@@ -44,7 +57,4 @@ public class IotProductSaveReqVO {
|
||||
@NotNull(message = "数据校验级别不能为空")
|
||||
private Integer validateType;
|
||||
|
||||
@Schema(description = "产品描述", example = "描述")
|
||||
private String description;
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class ThingModelRespVO {
|
||||
|
||||
/**
|
||||
* 产品编号
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 物模型
|
||||
*/
|
||||
private Model model;
|
||||
|
||||
/**
|
||||
* 物模型
|
||||
*/
|
||||
@Data
|
||||
public static class Model {
|
||||
|
||||
/**
|
||||
* 属性列表
|
||||
*/
|
||||
private List<ThingModelProperty> properties;
|
||||
|
||||
/**
|
||||
* 服务列表
|
||||
*/
|
||||
private List<ThingModelService> services;
|
||||
|
||||
/**
|
||||
* 事件列表
|
||||
*/
|
||||
private List<ThingModelEvent> events;
|
||||
}
|
||||
}
|
@@ -9,7 +9,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品物模型 Response VO")
|
||||
@Data
|
||||
|
@@ -0,0 +1,46 @@
|
||||
package cn.iocoder.yudao.module.iot.convert.device;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Mapper
|
||||
public interface IotDeviceDataConvert {
|
||||
|
||||
IotDeviceDataConvert INSTANCE = Mappers.getMapper(IotDeviceDataConvert.class);
|
||||
|
||||
// default List<IotDeviceDataRespVO> convert(Map<String, Object> deviceData, IotDeviceDO device){
|
||||
// List<IotDeviceDataRespVO> list = new ArrayList<>();
|
||||
// deviceData.forEach((identifier, value) -> {
|
||||
//// ThingModelProperty property = ThingModelService.INSTANCE.getProperty(device.getProductId(), identifier);
|
||||
//// if (Objects.isNull(property)) {
|
||||
//// return;
|
||||
//// }
|
||||
// IotDeviceDataRespVO vo = new IotDeviceDataRespVO();
|
||||
// vo.setDeviceId(device.getId());
|
||||
// vo.setProductKey(device.getProductKey());
|
||||
// vo.setDeviceName(device.getDeviceName());
|
||||
// vo.setIdentifier(identifier);
|
||||
//// vo.setName(property.getName());
|
||||
//// vo.setDataType(property.getDataType().getType());
|
||||
// vo.setUpdateTime(device.getUpdateTime());
|
||||
// vo.setValue(value.toString());
|
||||
// list.add(vo);
|
||||
// });
|
||||
// return list;
|
||||
// }
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
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.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* IoT 设备数据 DO
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceDataDO {
|
||||
|
||||
/**
|
||||
* 设备编号
|
||||
* <p>
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
*/
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 物模型编号
|
||||
* <p>
|
||||
* 关联 {@link IotThinkModelFunctionDO#getId()}
|
||||
*/
|
||||
private Long thinkModelFunctionId;
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
* <p>
|
||||
* 关联 {@link IotProductDO#getProductKey()}
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
* <p>
|
||||
* 冗余 {@link IotDeviceDO#getDeviceName()}
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 属性标识符
|
||||
* <p>
|
||||
* 关联 {@link IotThinkModelFunctionDO#getIdentifier()}
|
||||
*/
|
||||
private String identifier;
|
||||
|
||||
/**
|
||||
* 属性名称
|
||||
* <p>
|
||||
* 关联 {@link IotThinkModelFunctionDO#getName()}
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 数据类型
|
||||
* <p>
|
||||
* 关联 {@link IotThinkModelFunctionDO#getProperty()#getDataType()}
|
||||
*/
|
||||
private String dataType;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 最新值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* IoT 产品分类 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("iot_product_category")
|
||||
@KeySequence("iot_product_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotProductCategoryDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 分类 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 分类名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 分类排序
|
||||
*/
|
||||
private Integer sort;
|
||||
/**
|
||||
* 分类状态
|
||||
*
|
||||
* 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 分类描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
@@ -22,7 +22,7 @@ import lombok.*;
|
||||
public class IotProductDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 产品ID
|
||||
* 产品 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
@@ -30,17 +30,24 @@ public class IotProductDO extends BaseDO {
|
||||
* 产品名称
|
||||
*/
|
||||
private String name;
|
||||
// TODO @haohao:这个字段,要不改成 identifier,和阿里云更统一些
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
private String productKey;
|
||||
/**
|
||||
* 产品所属品类编号
|
||||
* 产品分类编号
|
||||
* <p>
|
||||
* TODO 外键:后续加
|
||||
* 关联 {@link IotProductCategoryDO#getId()}
|
||||
*/
|
||||
private Long categoryId;
|
||||
/**
|
||||
* 产品图标
|
||||
*/
|
||||
private String icon;
|
||||
/**
|
||||
* 产品图片
|
||||
*/
|
||||
private String picUrl;
|
||||
/**
|
||||
* 产品描述
|
||||
*/
|
||||
|
@@ -0,0 +1,91 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelDataType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* FieldParser 类用于解析和转换物模型字段到 TDengine 字段
|
||||
*/
|
||||
public class FieldParser {
|
||||
|
||||
/**
|
||||
* 物模型到td数据类型映射
|
||||
*/
|
||||
private static final HashMap<String, String> TYPE_MAPPING = new HashMap<>() {
|
||||
{
|
||||
put("INT", "INT");
|
||||
put("FLOAT", "FLOAT");
|
||||
put("DOUBLE", "DOUBLE");
|
||||
put("BOOL", "BOOL");
|
||||
put("ENUM", "NCHAR");
|
||||
put("TEXT", "NCHAR");
|
||||
put("DATE", "NCHAR");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将物模型字段转换为td字段
|
||||
*
|
||||
* @param property 物模型属性
|
||||
* @return TdField对象
|
||||
*/
|
||||
public static TdFieldDO parse(ThingModelProperty property) {
|
||||
String fieldName = property.getIdentifier().toLowerCase();
|
||||
ThingModelDataType type = property.getDataType();
|
||||
|
||||
// 将物模型字段类型映射为td字段类型
|
||||
String fType = TYPE_MAPPING.get(type.getType().toUpperCase());
|
||||
|
||||
// 如果字段类型为NCHAR,默认长度为64
|
||||
int dataLength = 0;
|
||||
if ("NCHAR".equals(fType)) {
|
||||
dataLength = 64;
|
||||
}
|
||||
return new TdFieldDO(fieldName, fType, dataLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从物模型中获取字段列表
|
||||
*
|
||||
* @param thingModel 物模型响应对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
public static List<TdFieldDO> parse(ThingModelRespVO thingModel) {
|
||||
return thingModel.getModel().getProperties().stream()
|
||||
.map(FieldParser::parse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将从库中查出来的字段信息转换为 TDengine 字段对象
|
||||
*
|
||||
* @param rows 从库中查出的字段信息列表
|
||||
* @return 转换后的 TDengine 字段对象列表
|
||||
*/
|
||||
public static List<TdFieldDO> parse(List<List<Object>> rows) {
|
||||
return rows.stream().map(row -> {
|
||||
String type = row.get(1).toString().toUpperCase();
|
||||
int dataLength = "NCHAR".equals(type) ? Integer.parseInt(row.get(2).toString()) : -1;
|
||||
return new TdFieldDO(
|
||||
row.get(0).toString(),
|
||||
type,
|
||||
dataLength
|
||||
);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段字义
|
||||
*/
|
||||
public static String getFieldDefine(TdFieldDO field) {
|
||||
return "`" + field.getFieldName() + "`" + " "
|
||||
+ (field.getDataLength() > 0 ? String.format("%s(%d)", field.getDataType(), field.getDataLength())
|
||||
: field.getDataType());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
// TODO @haohao:类似这个,其实可以参考 mybatis plus,querywrapper,搞个 TdEngineQueryWrapper。这样看起来会更好懂。
|
||||
/**
|
||||
* 查询DO
|
||||
*/
|
||||
@Data
|
||||
public class SelectDO {
|
||||
|
||||
// TODO @haoha:database 是个单词
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
private String dataBaseName;
|
||||
|
||||
/**
|
||||
* 超级表名称
|
||||
*/
|
||||
private String tableName;
|
||||
|
||||
/**
|
||||
* 查询字段
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private Long startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private Long endTime;
|
||||
|
||||
/**
|
||||
* 查询类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private String deviceId;
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
// TODO @haohao:类似 SelectDO 的想法,只是它是返回。ps:貌似可以在 tdengine 里面,创建一个 query 包,放这种比较特殊的查询和结果对象。dataobject 更多还是实际存储的结构化的 do
|
||||
@Data
|
||||
public class SelectVisualDO {
|
||||
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
private String dataBaseName;
|
||||
|
||||
/**
|
||||
* 表名
|
||||
*/
|
||||
private String tableName;
|
||||
|
||||
/**
|
||||
* 属性
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* 查询类型,0历史数据,1实时数据,2聚合数据
|
||||
*/
|
||||
private int type;
|
||||
|
||||
/**
|
||||
* 查询的数据量
|
||||
*/
|
||||
private int num;
|
||||
|
||||
/**
|
||||
* 聚合函数
|
||||
*/
|
||||
private String aggregate;
|
||||
|
||||
/**
|
||||
* 统计间隔数字+s/m/h/d
|
||||
* 比如1s,1m,1h,1d代表1秒,1分钟,1小时,1天
|
||||
*/
|
||||
private String interval;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private Long startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private Long endTime;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private Map<String, Object> params;
|
||||
|
||||
private String sql;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private Long lastTime;
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* tags查询DO
|
||||
*/
|
||||
@Data
|
||||
public class TagsSelectDO {
|
||||
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
private String dataBaseName;
|
||||
|
||||
/**
|
||||
* 超级表名称
|
||||
*/
|
||||
private String stableName;
|
||||
|
||||
/**
|
||||
* tags名称
|
||||
*/
|
||||
private String tagsName;
|
||||
|
||||
/**
|
||||
* tags值
|
||||
*/
|
||||
private Long startTime;
|
||||
|
||||
/**
|
||||
* tags值
|
||||
*/
|
||||
private Long endTime;
|
||||
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* TD 引擎的字段
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TdFieldDO {
|
||||
|
||||
/**
|
||||
* 字段名称
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
*/
|
||||
private String dataType;
|
||||
|
||||
/**
|
||||
* 字段长度
|
||||
*/
|
||||
private Integer dataLength = 0;
|
||||
|
||||
/**
|
||||
* 字段值
|
||||
*/
|
||||
private Object fieldValue;
|
||||
|
||||
public TdFieldDO(String fieldName, String dataType, Integer dataLength) {
|
||||
this.fieldName = fieldName;
|
||||
this.dataType = dataType;
|
||||
this.dataLength = dataLength;
|
||||
}
|
||||
|
||||
public TdFieldDO(String fieldName, Object fieldValue) {
|
||||
this.fieldName = fieldName;
|
||||
this.fieldValue = fieldValue;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TdResponse 类用于处理 TDengine 的响应
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TdResponse {
|
||||
|
||||
public static final int CODE_SUCCESS = 0;
|
||||
public static final int CODE_TB_NOT_EXIST = 866;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 列信息
|
||||
* [["time","TIMESTAMP",8,""],["powerstate","TINYINT",1,""],["brightness","INT",4,""],["deviceid","NCHAR",32,"TAG"]]
|
||||
*/
|
||||
private List data;
|
||||
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
// TODO @haohao:有部分非实体的部分,是不是可以搞到 iot 的 framework 包下,搞个 tdengine 包,作为框架级的封装,放在 dataobject,感觉不是很合理哈。【可以微信讨论下】
|
||||
/**
|
||||
* TdRestApi 类用于处理 TDengine 的 REST API 请求
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TdRestApi {
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
|
||||
private String url;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.password}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 获取 REST API URL
|
||||
*/
|
||||
private String getRestApiUrl() {
|
||||
String restUrl = url.replace("jdbc:TAOS-RS://", "")
|
||||
.replaceAll("\\?.*", "");
|
||||
int idx = restUrl.lastIndexOf("/");
|
||||
return String.format("%s/rest/sql/%s", restUrl.substring(0, idx), restUrl.substring(idx + 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 新建td api请求对象
|
||||
*/
|
||||
public HttpRequest newApiRequest(String sql) {
|
||||
return HttpRequest
|
||||
.post(getRestApiUrl())
|
||||
.body(sql)
|
||||
.basicAuth(username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql
|
||||
*/
|
||||
public TdResponse execSql(String sql) {
|
||||
log.info("exec td sql:{}", sql);
|
||||
HttpRequest request = newApiRequest(sql);
|
||||
HttpResponse response = request.execute();
|
||||
return JSONUtil.toBean(response.body(), TdResponse.class);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TD 引擎的数据库
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TdTableDO {
|
||||
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
private String dataBaseName;
|
||||
|
||||
// TODO @haohao:superTableName 和 tableName 是不是合并。因为每个 mapper 操作的时候,有且只会使用到其中一个。
|
||||
/**
|
||||
* 超级表名称
|
||||
*/
|
||||
private String superTableName;
|
||||
|
||||
/**
|
||||
* 表名称
|
||||
*/
|
||||
private String tableName;
|
||||
|
||||
/**
|
||||
* COLUMN 字段
|
||||
*/
|
||||
private TdFieldDO column;
|
||||
|
||||
/**
|
||||
* TAG 字段
|
||||
*/
|
||||
private TdFieldDO tag;
|
||||
|
||||
/**
|
||||
* COLUMN 字段 - 列表
|
||||
*/
|
||||
private List<TdFieldDO> columns;
|
||||
|
||||
/**
|
||||
* TAG 字段 - 列表
|
||||
*/
|
||||
private List<TdFieldDO> tags;
|
||||
|
||||
public TdTableDO(String dataBaseName, String superTableName, List<TdFieldDO> columns, List<TdFieldDO> tags) {
|
||||
this.dataBaseName = dataBaseName;
|
||||
this.superTableName = superTableName;
|
||||
this.columns = columns;
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public TdTableDO(String dataBaseName, String superTableName) {
|
||||
this.dataBaseName = dataBaseName;
|
||||
this.superTableName = superTableName;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 物模型消息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ThingModelMessage {
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 扩展功能的参数
|
||||
*/
|
||||
private Object sys;
|
||||
|
||||
/**
|
||||
* 请求方法 例如:thing.event.property.post
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private Object params;
|
||||
|
||||
/**
|
||||
* 属性上报时间戳
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
/**
|
||||
* 设备信息
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 设备 key
|
||||
*/
|
||||
private String deviceKey;
|
||||
|
||||
/**
|
||||
* 转换为 Map 类型
|
||||
*/
|
||||
public Map<String, Object> dataToMap() {
|
||||
Map<String, Object> mapData = new HashMap<>();
|
||||
if (params instanceof Map) {
|
||||
((Map<?, ?>) params).forEach((key, value) -> mapData.put(key.toString(), value));
|
||||
}
|
||||
return mapData;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.dal.mysql.device;
|
||||
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.device.vo.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
|
||||
|
||||
// TODO @haohao:可能多余的查询条件,要去掉哈
|
||||
default PageResult<IotDeviceDO> selectPage(IotDevicePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceDO>()
|
||||
.eqIfPresent(IotDeviceDO::getDeviceKey, reqVO.getDeviceKey())
|
||||
|
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.product;
|
||||
|
||||
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.product.vo.category.IotProductCategoryPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductCategoryDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 产品分类 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotProductCategoryMapper extends BaseMapperX<IotProductCategoryDO> {
|
||||
|
||||
default PageResult<IotProductCategoryDO> selectPage(IotProductCategoryPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotProductCategoryDO>()
|
||||
.likeIfPresent(IotProductCategoryDO::getName, reqVO.getName())
|
||||
.betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(IotProductCategoryDO::getId));
|
||||
}
|
||||
|
||||
default List<IotProductCategoryDO> selectListByStatus(Integer status) {
|
||||
return selectList(IotProductCategoryDO::getStatus, status);
|
||||
}
|
||||
|
||||
}
|
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.dal.mysql.product;
|
||||
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.product.vo.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
|
@@ -43,7 +43,7 @@ public interface IotThinkModelFunctionMapper extends BaseMapperX<IotThinkModelFu
|
||||
|
||||
default List<IotThinkModelFunctionDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
|
||||
List<String> identifiers,
|
||||
List<Integer> types){
|
||||
List<Integer> types) {
|
||||
return selectList(new LambdaQueryWrapperX<IotThinkModelFunctionDO>()
|
||||
.eq(IotThinkModelFunctionDO::getProductId, productId)
|
||||
.in(IotThinkModelFunctionDO::getIdentifier, identifiers)
|
||||
@@ -55,4 +55,8 @@ public interface IotThinkModelFunctionMapper extends BaseMapperX<IotThinkModelFu
|
||||
IotThinkModelFunctionDO::getName, name);
|
||||
}
|
||||
|
||||
default List<IotThinkModelFunctionDO> selectListByProductKey(String productKey) {
|
||||
return selectList(IotThinkModelFunctionDO::getProductKey, productKey);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.redis;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Iot Redis Key 枚举类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface RedisKeyConstants {
|
||||
|
||||
/**
|
||||
* 设备属性数据缓存
|
||||
* <p>
|
||||
* KEY 格式:device_property_data:{deviceId}
|
||||
* VALUE 数据类型:String 设备属性数据
|
||||
*/
|
||||
String DEVICE_PROPERTY_DATA = "device_property_data:%s_%s_%s";
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.redis.deviceData;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants.DEVICE_PROPERTY_DATA;
|
||||
|
||||
/**
|
||||
* {@link IotDeviceDataDO} 的 Redis DAO
|
||||
*/
|
||||
@Repository
|
||||
public class DeviceDataRedisDAO {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public IotDeviceDataDO get(String productKey, String deviceName, String identifier) {
|
||||
String redisKey = formatKey(productKey, deviceName, identifier);
|
||||
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), IotDeviceDataDO.class);
|
||||
}
|
||||
|
||||
public void set(IotDeviceDataDO deviceData) {
|
||||
String redisKey = formatKey(deviceData.getProductKey(), deviceData.getDeviceName(), deviceData.getIdentifier());
|
||||
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(deviceData));
|
||||
}
|
||||
|
||||
public void delete(String productKey, String deviceName, String identifier) {
|
||||
String redisKey = formatKey(productKey, deviceName, identifier);
|
||||
stringRedisTemplate.delete(redisKey);
|
||||
}
|
||||
|
||||
private static String formatKey(String productKey, String deviceName, String identifier) {
|
||||
return String.format(DEVICE_PROPERTY_DATA, productKey, deviceName, identifier);
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.tdengine;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 专门处理 DDL(数据定义语言)操作,包含所有的数据库和表的定义操作,例如创建数据库、创建超级表、创建子表等
|
||||
*/
|
||||
@Mapper
|
||||
@DS("tdengine")
|
||||
public interface TdEngineDDLMapper {
|
||||
|
||||
/**
|
||||
* 创建数据库
|
||||
* SQL:CREATE DATABASE [IF NOT EXISTS] db_name [database_options];
|
||||
*
|
||||
* @param dataBaseName 数据库名称
|
||||
*/
|
||||
@TenantIgnore
|
||||
void createDatabase(@Param("dataBaseName") String dataBaseName);
|
||||
|
||||
/**
|
||||
* 创建超级表
|
||||
* SQL:CREATE STABLE [IF NOT EXISTS] stb_name (create_definition [, create_definition] ...) TAGS (create_definition [, create_definition] ...) [table_options];
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* columns 列信息
|
||||
* tags 标签信息
|
||||
*/
|
||||
@TenantIgnore
|
||||
void createSuperTable(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 查看超级表 - 显示当前数据库下的所有超级表信息
|
||||
* SQL:SHOW STABLES [LIKE tb_name_wildcard];
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> showSuperTables(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 查看超级表 - 获取超级表的结构信息
|
||||
* SQL:DESCRIBE [db_name.]stb_name;
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> describeSuperTable(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 修改超级表 - 增加列
|
||||
* SQL:ALTER STABLE stb_name ADD COLUMN col_name column_type;
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* column 列信息
|
||||
*/
|
||||
@TenantIgnore
|
||||
void addColumnForSuperTable(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 修改超级表 - 删除列
|
||||
* SQL:ALTER STABLE stb_name DROP COLUMN col_name;
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* column 列信息
|
||||
*/
|
||||
@TenantIgnore
|
||||
void dropColumnForSuperTable(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 修改超级表 - 修改列宽
|
||||
* SQL:ALTER STABLE stb_name MODIFY COLUMN col_name data_type(length);
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* column 列信息
|
||||
*/
|
||||
@TenantIgnore
|
||||
void modifyColumnWidthForSuperTable(TdTableDO superTable);
|
||||
|
||||
|
||||
/**
|
||||
* 修改超级表 - 为超级表添加标签
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* tag 标签信息
|
||||
*/
|
||||
@TenantIgnore
|
||||
void addTagForSuperTable(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 修改超级表 - 为超级表删除标签
|
||||
*
|
||||
* @param superTable 超级表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* tag 标签信息
|
||||
*/
|
||||
@TenantIgnore
|
||||
void dropTagForSuperTable(TdTableDO superTable);
|
||||
|
||||
/**
|
||||
* 创建子表 - 创建子表
|
||||
* SQL:CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name TAGS (tag_value1, ...);
|
||||
*
|
||||
* @param table 表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* tableName 子表名称
|
||||
* tags TAG 字段
|
||||
*/
|
||||
@TenantIgnore
|
||||
void createTable(TdTableDO table);
|
||||
|
||||
/**
|
||||
* 创建子表 - 创建子表并指定标签的值
|
||||
* SQL:CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name (tag_name1, ...) TAGS (tag_value1, ...);
|
||||
*
|
||||
* @param table 表信息
|
||||
* dataBaseName 数据库名称
|
||||
* superTableName 超级表名称
|
||||
* tableName 子表名称
|
||||
* tags TAG 字段
|
||||
*/
|
||||
@TenantIgnore
|
||||
void createTableWithTags(TdTableDO table);
|
||||
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.tdengine;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TagsSelectDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 专门处理 TD Engine 的 DML(数据操作语言)操作,处理所有的数据查询和写入操作,如插入数据、查询数据等
|
||||
*/
|
||||
@Mapper
|
||||
@DS("tdengine")
|
||||
public interface TdEngineDMLMapper {
|
||||
|
||||
/**
|
||||
* 插入数据 - 指定列插入数据
|
||||
*
|
||||
* @param table 数据
|
||||
* dataBaseName 数据库名
|
||||
* tableName 表名
|
||||
* columns 列
|
||||
*/
|
||||
@TenantIgnore
|
||||
void insertData(TdTableDO table);
|
||||
|
||||
/**
|
||||
* 根据时间戳查询数据
|
||||
*
|
||||
* @param selectDO 查询条件
|
||||
* @return 查询结果列表
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> selectByTimestamp(SelectDO selectDO);
|
||||
|
||||
/**
|
||||
* 根据时间戳获取数据条数
|
||||
*
|
||||
* @param selectDO 查询条件
|
||||
* @return 数据条数
|
||||
*/
|
||||
@TenantIgnore
|
||||
Map<String, Long> selectCountByTimestamp(SelectDO selectDO);
|
||||
|
||||
/**
|
||||
* 获取最新数据
|
||||
*
|
||||
* @param selectDO 查询条件
|
||||
* @return 最新数据
|
||||
*/
|
||||
@TenantIgnore
|
||||
Map<String, Object> selectOneLastData(SelectDO selectDO);
|
||||
|
||||
/**
|
||||
* 获取历史数据列表
|
||||
*
|
||||
* @param selectVisualDO 查询条件
|
||||
* @return 历史数据列表
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> selectHistoryDataList(SelectVisualDO selectVisualDO);
|
||||
|
||||
/**
|
||||
* 获取实时数据列表
|
||||
*
|
||||
* @param selectVisualDO 查询条件
|
||||
* @return 实时数据列表
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> selectRealtimeDataList(SelectVisualDO selectVisualDO);
|
||||
|
||||
/**
|
||||
* 获取聚合数据列表
|
||||
*
|
||||
* @param selectVisualDO 查询条件
|
||||
* @return 聚合数据列表
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> selectAggregateDataList(SelectVisualDO selectVisualDO);
|
||||
|
||||
/**
|
||||
* 根据标签获取最新数据列表
|
||||
*
|
||||
* @param tagsSelectDO 查询条件
|
||||
* @return 最新数据列表
|
||||
*/
|
||||
@TenantIgnore
|
||||
List<Map<String, Object>> selectLastDataListByTags(TagsSelectDO tagsSelectDO);
|
||||
|
||||
/**
|
||||
* 获取历史数据条数
|
||||
*
|
||||
* @param selectVisualDO 查询条件
|
||||
* @return 数据条数
|
||||
*/
|
||||
@TenantIgnore
|
||||
Long selectHistoryCount(SelectVisualDO selectVisualDO);
|
||||
}
|
@@ -1,8 +1,12 @@
|
||||
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 jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
// TODO @芋艿:在瞅瞅
|
||||
@@ -16,13 +20,22 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class EmqxServiceImpl implements EmqxService {
|
||||
|
||||
@Resource
|
||||
private IotDeviceDataService iotDeviceDataService;
|
||||
|
||||
// TODO 多线程处理消息
|
||||
@Override
|
||||
@Async
|
||||
public void subscribeCallback(String topic, MqttMessage mqttMessage) {
|
||||
log.info("收到消息,主题: {}, 内容: {}", topic, new String(mqttMessage.getPayload()));
|
||||
// 根据不同的主题,处理不同的业务逻辑
|
||||
if (topic.contains("/property/post")) {
|
||||
// 设备上报数据
|
||||
// 设备上报数据 topic /sys/f13f57c63e9/dianbiao1/thing/event/property/post
|
||||
// TODO @hao:这块未来可能,搞个 IotTopicUrls 之类?把拼接和解析的逻辑,收敛
|
||||
String productKey = topic.split("/")[2];
|
||||
String deviceName = topic.split("/")[3];
|
||||
String message = new String(mqttMessage.getPayload());
|
||||
iotDeviceDataService.saveDeviceData(productKey, deviceName, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +43,11 @@ public class EmqxServiceImpl implements EmqxService {
|
||||
public void subscribe(MqttClient client) {
|
||||
try {
|
||||
// 订阅默认主题,可以根据需要修改
|
||||
// client.subscribe("$share/yudao/+/+/#", 1);
|
||||
client.subscribe("/sys/+/+/#", 1);
|
||||
log.info("订阅默认主题成功");
|
||||
} catch (Exception e) {
|
||||
log.error("订阅默认主题失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TaosAspect 是一个处理 Taos 数据库返回值的切面。
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TaosAspect {
|
||||
|
||||
@Around("execution(java.util.Map<String,Object> cn.iocoder.yudao.module.iot.dal.tdengine.*.*(..))")
|
||||
public Object handleType(ProceedingJoinPoint joinPoint) {
|
||||
Map<String, Object> result = null;
|
||||
try {
|
||||
result = (Map<String, Object>) joinPoint.proceed();
|
||||
result.replaceAll((key, value) -> {
|
||||
if (value instanceof byte[]) {
|
||||
return new String((byte[]) value);
|
||||
} else if (value instanceof Timestamp) {
|
||||
return ((Timestamp) value).getTime();
|
||||
}
|
||||
return value;
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
log.error("TaosAspect handleType error", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device;
|
||||
|
||||
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.dal.dataobject.device.IotDeviceDataDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 设备数据 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotDeviceDataService {
|
||||
|
||||
|
||||
/**
|
||||
* 保存设备数据
|
||||
*
|
||||
* @param productKey 产品 key
|
||||
* @param deviceName 设备名称
|
||||
* @param message 消息
|
||||
*/
|
||||
void saveDeviceData(String productKey, String deviceName, String message);
|
||||
|
||||
/**
|
||||
* 获得设备属性最新数据
|
||||
*
|
||||
* @param deviceId 设备编号
|
||||
* @return 设备属性最新数据
|
||||
*/
|
||||
List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceId);
|
||||
|
||||
/**
|
||||
* 获得设备属性历史数据
|
||||
*
|
||||
* @param deviceDataReqVO 设备属性历史数据 Request VO
|
||||
* @return 设备属性历史数据
|
||||
*/
|
||||
PageResult<Map<String, Object>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO);
|
||||
}
|
@@ -0,0 +1,139 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
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.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
|
||||
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.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
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.product.IotProductFunctionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
|
||||
import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class IotDeviceDataServiceImpl implements IotDeviceDataService {
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
|
||||
private String url;
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotThingModelMessageService thingModelMessageService;
|
||||
@Resource
|
||||
private IotThinkModelFunctionService thinkModelFunctionService;
|
||||
@Resource
|
||||
private TdEngineDMLMapper tdEngineDMLMapper;
|
||||
|
||||
@Resource
|
||||
private DeviceDataRedisDAO deviceDataRedisDAO;
|
||||
|
||||
@Override
|
||||
public void saveDeviceData(String productKey, String deviceName, String message) {
|
||||
// 1. 根据产品 key 和设备名称,获得设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName);
|
||||
// 2. 解析消息,保存数据
|
||||
JSONObject jsonObject = new JSONObject(message);
|
||||
log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject);
|
||||
ThingModelMessage thingModelMessage = ThingModelMessage.builder()
|
||||
.id(jsonObject.getStr("id"))
|
||||
.sys(jsonObject.get("sys"))
|
||||
.method(jsonObject.getStr("method"))
|
||||
.params(jsonObject.get("params"))
|
||||
.time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
|
||||
.productKey(productKey)
|
||||
.deviceName(deviceName)
|
||||
.deviceKey(device.getDeviceKey())
|
||||
.build();
|
||||
thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
|
||||
List<IotDeviceDataDO> list = new ArrayList<>();
|
||||
// 1. 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
|
||||
// 2. 获取设备属性最新数据
|
||||
List<IotThinkModelFunctionDO> thinkModelFunctionList = thinkModelFunctionService.getThinkModelFunctionListByProductKey(device.getProductKey());
|
||||
thinkModelFunctionList = thinkModelFunctionList.stream()
|
||||
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.getType()
|
||||
.equals(function.getType())).toList();
|
||||
|
||||
// 3. 过滤标识符和属性名称
|
||||
if (deviceDataReqVO.getIdentifier() != null) {
|
||||
thinkModelFunctionList = thinkModelFunctionList.stream()
|
||||
.filter(function -> function.getIdentifier().toLowerCase().contains(deviceDataReqVO.getIdentifier().toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
if (deviceDataReqVO.getName() != null) {
|
||||
thinkModelFunctionList = thinkModelFunctionList.stream()
|
||||
.filter(function -> function.getName().toLowerCase().contains(deviceDataReqVO.getName().toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
// 4. 获取设备属性最新数据
|
||||
thinkModelFunctionList.forEach(function -> {
|
||||
IotDeviceDataDO deviceData = deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName(), function.getIdentifier());
|
||||
if (deviceData == null) {
|
||||
deviceData = new IotDeviceDataDO();
|
||||
deviceData.setProductKey(device.getProductKey());
|
||||
deviceData.setDeviceName(device.getDeviceName());
|
||||
deviceData.setIdentifier(function.getIdentifier());
|
||||
deviceData.setDeviceId(deviceDataReqVO.getDeviceId());
|
||||
deviceData.setThinkModelFunctionId(function.getId());
|
||||
deviceData.setName(function.getName());
|
||||
deviceData.setDataType(function.getProperty().getDataType().getType());
|
||||
}
|
||||
list.add(deviceData);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<Map<String, Object>> getHistoryDeviceProperties(IotDeviceDataPageReqVO deviceDataReqVO) {
|
||||
PageResult<Map<String, Object>> pageResult = new PageResult<>();
|
||||
// 1. 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
|
||||
// 2. 获取设备属性历史数据
|
||||
SelectVisualDO selectVisualDO = new SelectVisualDO();
|
||||
selectVisualDO.setDataBaseName(getDatabaseName());
|
||||
selectVisualDO.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
|
||||
selectVisualDO.setFieldName(deviceDataReqVO.getIdentifier());
|
||||
selectVisualDO.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
|
||||
selectVisualDO.setEndTime(DateUtil.date(deviceDataReqVO.getTimes()[1].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("rows", deviceDataReqVO.getPageSize());
|
||||
params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize());
|
||||
selectVisualDO.setParams(params);
|
||||
pageResult.setList(tdEngineDMLMapper.selectHistoryDataList(selectVisualDO));
|
||||
pageResult.setTotal(tdEngineDMLMapper.selectHistoryCount(selectVisualDO));
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
private String getDatabaseName() {
|
||||
return StrUtil.subAfter(url, "/", true);
|
||||
}
|
||||
|
||||
private static String getDeviceTableName(String productKey, String deviceName) {
|
||||
return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey, deviceName);
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
|
||||
import jakarta.validation.*;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
@@ -64,4 +66,13 @@ public interface IotDeviceService {
|
||||
* @return 设备数量
|
||||
*/
|
||||
Long getDeviceCountByProductId(Long productId);
|
||||
|
||||
/**
|
||||
* 根据产品 key 和设备名称,获得设备信息
|
||||
*
|
||||
* @param productKey 产品 key
|
||||
* @param deviceName 设备名称
|
||||
* @return 设备信息
|
||||
*/
|
||||
IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
|
||||
}
|
@@ -4,9 +4,10 @@ import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
|
||||
@@ -18,7 +19,10 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@@ -32,7 +36,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class DeviceServiceImpl implements IotDeviceService {
|
||||
public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
|
||||
@Resource
|
||||
private IotDeviceMapper deviceMapper;
|
||||
@@ -135,8 +139,11 @@ public class DeviceServiceImpl implements IotDeviceService {
|
||||
* @return 生成的 MQTT Password
|
||||
*/
|
||||
private String generateMqttPassword() {
|
||||
// TODO @haohao:【后续优化】在实际应用中,建议使用更安全的方法生成 MQTT Password,如加密或哈希
|
||||
return UUID.randomUUID().toString();
|
||||
// TODO @浩浩:这里的 StrUtil 随机字符串?
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] passwordBytes = new byte[32]; // 256 位的随机数
|
||||
secureRandom.nextBytes(passwordBytes);
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(passwordBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,12 +218,29 @@ public class DeviceServiceImpl implements IotDeviceService {
|
||||
|
||||
@Override
|
||||
public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateDeviceExists(updateReqVO.getId());
|
||||
// 1. 校验存在
|
||||
IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
|
||||
|
||||
// 更新状态和更新时间
|
||||
IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
|
||||
deviceMapper.updateById(updateObj);
|
||||
// 2.1 更新状态和更新时间
|
||||
IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
|
||||
// 2.2 更新状态相关时间
|
||||
if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
|
||||
&& Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
|
||||
// 从未激活到在线,设置激活时间和最后上线时间
|
||||
updateDevice.setActiveTime(LocalDateTime.now());
|
||||
updateDevice.setLastOnlineTime(LocalDateTime.now());
|
||||
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
|
||||
// 如果是上线,设置最后上线时间
|
||||
updateDevice.setLastOnlineTime(LocalDateTime.now());
|
||||
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.OFFLINE.getStatus())) {
|
||||
// 如果是离线,设置最后离线时间
|
||||
updateDevice.setLastOfflineTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 2.3 设置状态更新时间
|
||||
updateDevice.setStatusLastUpdateTime(LocalDateTime.now());
|
||||
// 2.4 更新到数据库
|
||||
deviceMapper.updateById(updateDevice);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -224,4 +248,10 @@ public class DeviceServiceImpl implements IotDeviceService {
|
||||
return deviceMapper.selectCountByProductId(productId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TenantIgnore
|
||||
public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) {
|
||||
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
package cn.iocoder.yudao.module.iot.service.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.category.IotProductCategoryPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.category.IotProductCategorySaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductCategoryDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
/**
|
||||
* IoT 产品分类 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotProductCategoryService {
|
||||
|
||||
/**
|
||||
* 创建产品分类
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createProductCategory(@Valid IotProductCategorySaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新产品分类
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateProductCategory(@Valid IotProductCategorySaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除产品分类
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteProductCategory(Long id);
|
||||
|
||||
/**
|
||||
* 获得产品分类
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 产品分类
|
||||
*/
|
||||
IotProductCategoryDO getProductCategory(Long id);
|
||||
|
||||
/**
|
||||
* 获得产品分类列表
|
||||
*
|
||||
* @param ids 编号
|
||||
* @return 产品分类列表
|
||||
*/
|
||||
List<IotProductCategoryDO> getProductCategoryList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得产品分类 Map
|
||||
*
|
||||
* @param ids 编号
|
||||
* @return 产品分类 Map
|
||||
*/
|
||||
default Map<Long, IotProductCategoryDO> getProductCategoryMap(Collection<Long> ids) {
|
||||
return convertMap(getProductCategoryList(ids), IotProductCategoryDO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得产品分类分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 产品分类分页
|
||||
*/
|
||||
PageResult<IotProductCategoryDO> getProductCategoryPage(IotProductCategoryPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得产品分类列表,根据状态
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 产品分类列表
|
||||
*/
|
||||
List<IotProductCategoryDO> getProductCategoryListByStatus(Integer status);
|
||||
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
package cn.iocoder.yudao.module.iot.service.product;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.product.vo.category.IotProductCategoryPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.category.IotProductCategorySaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductCategoryDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductCategoryMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_CATEGORY_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* IoT 产品分类 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class IotProductCategoryServiceImpl implements IotProductCategoryService {
|
||||
|
||||
@Resource
|
||||
private IotProductCategoryMapper productCategoryMapper;
|
||||
|
||||
@Override
|
||||
public Long createProductCategory(IotProductCategorySaveReqVO createReqVO) {
|
||||
// 插入
|
||||
IotProductCategoryDO productCategory = BeanUtils.toBean(createReqVO, IotProductCategoryDO.class);
|
||||
productCategoryMapper.insert(productCategory);
|
||||
// 返回
|
||||
return productCategory.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProductCategory(IotProductCategorySaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateProductCategoryExists(updateReqVO.getId());
|
||||
// 更新
|
||||
IotProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, IotProductCategoryDO.class);
|
||||
productCategoryMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProductCategory(Long id) {
|
||||
// 校验存在
|
||||
validateProductCategoryExists(id);
|
||||
// 删除
|
||||
productCategoryMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateProductCategoryExists(Long id) {
|
||||
if (productCategoryMapper.selectById(id) == null) {
|
||||
throw exception(PRODUCT_CATEGORY_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotProductCategoryDO getProductCategory(Long id) {
|
||||
return productCategoryMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotProductCategoryDO> getProductCategoryList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return productCategoryMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotProductCategoryDO> getProductCategoryPage(IotProductCategoryPageReqVO pageReqVO) {
|
||||
return productCategoryMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotProductCategoryDO> getProductCategoryListByStatus(Integer status) {
|
||||
return productCategoryMapper.selectListByStatus(status);
|
||||
}
|
||||
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package cn.iocoder.yudao.module.iot.service.product;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO;
|
||||
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 jakarta.validation.Valid;
|
||||
|
||||
|
@@ -1,20 +1,21 @@
|
||||
package cn.iocoder.yudao.module.iot.service.product;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.product.vo.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO;
|
||||
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.thinkmodelfunction.IotThinkModelFunctionService;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
@@ -31,34 +32,23 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
@Resource
|
||||
private IotProductMapper productMapper;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private IotThinkModelFunctionService thinkModelFunctionService;
|
||||
|
||||
@Override
|
||||
public Long createProduct(IotProductSaveReqVO createReqVO) {
|
||||
// 1. 生成 ProductKey
|
||||
createProductKey(createReqVO);
|
||||
if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
|
||||
throw exception(PRODUCT_KEY_EXISTS);
|
||||
}
|
||||
// 2. 插入
|
||||
IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class);
|
||||
IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
|
||||
.setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
|
||||
productMapper.insert(product);
|
||||
return product.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ProductKey
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
*/
|
||||
private void createProductKey(IotProductSaveReqVO createReqVO) {
|
||||
String productKey = createReqVO.getProductKey();
|
||||
// 1. productKey为空,生成随机的 11 位字符串
|
||||
if (StrUtil.isEmpty(productKey)) {
|
||||
productKey = UUID.randomUUID().toString().replace("-", "").substring(0, 11);
|
||||
}
|
||||
// 2. 校验唯一性
|
||||
if (productMapper.selectByProductKey(productKey) != null) {
|
||||
throw exception(PRODUCT_IDENTIFICATION_EXISTS);
|
||||
}
|
||||
createReqVO.setProductKey(productKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProduct(IotProductSaveReqVO updateReqVO) {
|
||||
updateReqVO.setProductKey(null); // 不更新产品标识
|
||||
@@ -90,7 +80,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
}
|
||||
|
||||
private void validateProductStatus(IotProductDO iotProductDO) {
|
||||
if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getType())) {
|
||||
if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
|
||||
throw exception(PRODUCT_STATUS_NOT_DELETE);
|
||||
}
|
||||
}
|
||||
@@ -106,11 +96,17 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@DSTransactional(rollbackFor = Exception.class)
|
||||
public void updateProductStatus(Long id, Integer status) {
|
||||
// 1. 校验存在
|
||||
validateProductExists(id);
|
||||
// 2. 更新
|
||||
IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build();
|
||||
// 3. 产品是发布状态
|
||||
if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) {
|
||||
// 3.1 创建超级表数据模型
|
||||
thinkModelFunctionService.createSuperTableDataModel(id);
|
||||
}
|
||||
productMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,18 @@
|
||||
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.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 超级表服务,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
|
||||
*/
|
||||
public interface IotSuperTableService {
|
||||
|
||||
/**
|
||||
* 创建超级表数据模型
|
||||
*/
|
||||
void createSuperTableDataModel(IotProductDO product, List<IotThinkModelFunctionDO> functionList);
|
||||
}
|
@@ -0,0 +1,261 @@
|
||||
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.thinkmodelfunction.thingModel.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.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.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
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.product.IotProductFunctionTypeEnum;
|
||||
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<IotThinkModelFunctionDO> functionList) {
|
||||
ThingModelRespVO thingModel = buildThingModel(product, functionList);
|
||||
|
||||
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<IotThinkModelFunctionDO> functionList) {
|
||||
ThingModelRespVO thingModel = new ThingModelRespVO();
|
||||
thingModel.setId(product.getId());
|
||||
thingModel.setProductKey(product.getProductKey());
|
||||
|
||||
List<ThingModelProperty> properties = functionList.stream()
|
||||
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.equals(
|
||||
IotProductFunctionTypeEnum.valueOfType(function.getType())))
|
||||
.map(this::buildThingModelProperty)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
ThingModelRespVO.Model model = new ThingModelRespVO.Model();
|
||||
model.setProperties(properties);
|
||||
thingModel.setModel(model);
|
||||
|
||||
return thingModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建物模型属性
|
||||
*/
|
||||
private ThingModelProperty buildThingModelProperty(IotThinkModelFunctionDO function) {
|
||||
ThingModelProperty property = BeanUtil.copyProperties(function, ThingModelProperty.class);
|
||||
property.setDataType(function.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();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.yudao.module.iot.service.tdengine;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
|
||||
|
||||
/**
|
||||
* 物模型消息 Service
|
||||
*/
|
||||
public interface IotThingModelMessageService {
|
||||
|
||||
/**
|
||||
* 保存物模型消息
|
||||
*
|
||||
* @param device 设备
|
||||
* @param thingModelMessage 物模型消息
|
||||
*/
|
||||
void saveThingModelMessage(IotDeviceDO device, ThingModelMessage thingModelMessage);
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
package cn.iocoder.yudao.module.iot.service.tdengine;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
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.product.IotProductFunctionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 物模型消息 Service 实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class IotThingModelMessageServiceImpl implements IotThingModelMessageService {
|
||||
|
||||
private static final String TAG_NOTE = "TAG";
|
||||
private static final String NOTE = "note";
|
||||
private static final String TIME = "time";
|
||||
private static final String DEVICE_KEY = "device_key";
|
||||
private static final String DEVICE_NAME = "device_name";
|
||||
private static final String PRODUCT_KEY = "product_key";
|
||||
private static final String DEVICE_TYPE = "device_type";
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
|
||||
private String url;
|
||||
|
||||
@Resource
|
||||
private IotThinkModelFunctionService iotThinkModelFunctionService;
|
||||
@Resource
|
||||
private IotDeviceService iotDeviceService;
|
||||
@Resource
|
||||
private TdEngineDDLMapper tdEngineDDLMapper;
|
||||
@Resource
|
||||
private TdEngineDMLMapper tdEngineDMLMapper;
|
||||
|
||||
@Resource
|
||||
private DeviceDataRedisDAO deviceDataRedisDAO;
|
||||
|
||||
// TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感
|
||||
@Override
|
||||
@TenantIgnore
|
||||
public void saveThingModelMessage(IotDeviceDO device, ThingModelMessage thingModelMessage) {
|
||||
// 1. 判断设备状态,如果为未激活状态,创建数据表并更新设备状态
|
||||
if (IotDeviceStatusEnum.INACTIVE.getStatus().equals(device.getStatus())) {
|
||||
createDeviceTable(device.getDeviceType(), device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
|
||||
iotDeviceService.updateDeviceStatus(new IotDeviceStatusUpdateReqVO()
|
||||
.setId(device.getId()).setStatus(IotDeviceStatusEnum.ONLINE.getStatus()));
|
||||
}
|
||||
|
||||
// 2. 获取设备属性并进行物模型校验,过滤非物模型属性
|
||||
Map<String, Object> params = thingModelMessage.dataToMap();
|
||||
List<IotThinkModelFunctionDO> functionList = getValidFunctionList(thingModelMessage.getProductKey());
|
||||
if (functionList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 过滤并收集有效的属性字段,缓存设备属性
|
||||
List<TdFieldDO> schemaFieldValues = filterAndCollectValidFields(params, functionList, device, thingModelMessage.getTime());
|
||||
if (schemaFieldValues.size() == 1) { // 仅有时间字段,无需保存
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 构建并保存设备属性数据
|
||||
tdEngineDMLMapper.insertData(TdTableDO.builder()
|
||||
.dataBaseName(getDatabaseName())
|
||||
.tableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()))
|
||||
.columns(schemaFieldValues)
|
||||
.build());
|
||||
}
|
||||
|
||||
private List<IotThinkModelFunctionDO> getValidFunctionList(String productKey) {
|
||||
return iotThinkModelFunctionService
|
||||
.getThinkModelFunctionListByProductKey(productKey)
|
||||
.stream()
|
||||
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.getType().equals(function.getType()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotThinkModelFunctionDO> functionList, IotDeviceDO device, Long time) {
|
||||
// 1. 获取属性标识符集合
|
||||
Set<String> propertyIdentifiers = CollectionUtils.convertSet(functionList, IotThinkModelFunctionDO::getIdentifier);
|
||||
|
||||
// 2. 构建属性标识符和属性的映射
|
||||
Map<String, IotThinkModelFunctionDO> functionMap = functionList.stream()
|
||||
.collect(Collectors.toMap(IotThinkModelFunctionDO::getIdentifier, function -> function));
|
||||
|
||||
// 3. 过滤并收集有效的属性字段
|
||||
List<TdFieldDO> schemaFieldValues = new ArrayList<>();
|
||||
schemaFieldValues.add(new TdFieldDO(TIME, time));
|
||||
params.forEach((key, val) -> {
|
||||
if (propertyIdentifiers.contains(key)) {
|
||||
schemaFieldValues.add(new TdFieldDO(key.toLowerCase(), val));
|
||||
// 缓存设备属性
|
||||
// TODO @haohao:这个缓存的写入,可以使用的时候 cache 么?被动读
|
||||
setDeviceDataCache(device, functionMap.get(key), val, time);
|
||||
}
|
||||
});
|
||||
return schemaFieldValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存设备属性
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param iotThinkModelFunctionDO 物模型属性
|
||||
* @param val 属性值
|
||||
* @param time 时间
|
||||
*/
|
||||
private void setDeviceDataCache(IotDeviceDO device, IotThinkModelFunctionDO iotThinkModelFunctionDO, Object val, Long time) {
|
||||
IotDeviceDataDO deviceData = IotDeviceDataDO.builder()
|
||||
.productKey(device.getProductKey())
|
||||
.deviceName(device.getDeviceName())
|
||||
.identifier(iotThinkModelFunctionDO.getIdentifier())
|
||||
.value(val != null ? val.toString() : null)
|
||||
.updateTime(DateUtil.toLocalDateTime(new Date(time)))
|
||||
.deviceId(device.getId())
|
||||
.thinkModelFunctionId(iotThinkModelFunctionDO.getId())
|
||||
.name(iotThinkModelFunctionDO.getName())
|
||||
.dataType(iotThinkModelFunctionDO.getProperty().getDataType().getType())
|
||||
.build();
|
||||
deviceDataRedisDAO.set(deviceData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备数据表
|
||||
*
|
||||
* @param deviceType 设备类型
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @param deviceKey 设备 Key
|
||||
*/
|
||||
private void createDeviceTable(Integer deviceType, String productKey, String deviceName, String deviceKey) {
|
||||
// 1. 获取超级表名和数据库名
|
||||
String superTableName = getProductPropertySTableName(deviceType, productKey);
|
||||
String dataBaseName = getDatabaseName();
|
||||
|
||||
// 2. 获取超级表的结构信息
|
||||
List<Map<String, Object>> maps = tdEngineDDLMapper.describeSuperTable(new TdTableDO(dataBaseName, superTableName));
|
||||
List<TdFieldDO> tagsFieldValues = new ArrayList<>();
|
||||
if (maps != null) {
|
||||
// 2.1 过滤出 TAG 类型的字段
|
||||
List<Map<String, Object>> taggedNotesList = CollectionUtils.filterList(maps, map -> TAG_NOTE.equals(map.get(NOTE)));
|
||||
|
||||
// 2.2 解析字段信息
|
||||
tagsFieldValues = FieldParser.parse(taggedNotesList.stream()
|
||||
.map(map -> List.of(map.get("field"), map.get("type"), map.get("length")))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
// 2.3 设置 TAG 字段的值
|
||||
for (TdFieldDO tagsFieldValue : tagsFieldValues) {
|
||||
switch (tagsFieldValue.getFieldName()) {
|
||||
case PRODUCT_KEY -> tagsFieldValue.setFieldValue(productKey);
|
||||
case DEVICE_KEY -> tagsFieldValue.setFieldValue(deviceKey);
|
||||
case DEVICE_NAME -> tagsFieldValue.setFieldValue(deviceName);
|
||||
case DEVICE_TYPE -> tagsFieldValue.setFieldValue(deviceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 创建设备数据表
|
||||
String tableName = getDeviceTableName(productKey, deviceName);
|
||||
tdEngineDDLMapper.createTable(TdTableDO.builder().build()
|
||||
.setDataBaseName(dataBaseName)
|
||||
.setSuperTableName(superTableName)
|
||||
.setTableName(tableName)
|
||||
.setTags(tagsFieldValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库名称
|
||||
*
|
||||
* @return 数据库名称
|
||||
*/
|
||||
private String getDatabaseName() {
|
||||
return StrUtil.subAfter(url, "/", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品属性表名
|
||||
*
|
||||
* @param deviceType 设备类型
|
||||
* @param productKey 产品 Key
|
||||
* @return 产品属性表名
|
||||
*/
|
||||
private static String getProductPropertySTableName(Integer deviceType, String productKey) {
|
||||
// TODO @haohao:枚举下,会好点哈。
|
||||
return switch (deviceType) {
|
||||
case 1 -> String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
|
||||
case 2 -> String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
|
||||
default -> String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备表名
|
||||
*
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 设备表名
|
||||
*/
|
||||
private static String getDeviceTableName(String productKey, String deviceName) {
|
||||
return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
|
||||
}
|
||||
|
||||
}
|
@@ -62,4 +62,18 @@ public interface IotThinkModelFunctionService {
|
||||
*/
|
||||
PageResult<IotThinkModelFunctionDO> getThinkModelFunctionPage(IotThinkModelFunctionPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 创建超级表数据模型
|
||||
*
|
||||
* @param productId 产品编号
|
||||
*/
|
||||
void createSuperTableDataModel(Long productId);
|
||||
|
||||
/**
|
||||
* 获得产品物模型列表
|
||||
*
|
||||
* @param productKey 产品 Key
|
||||
* @return 产品物模型列表
|
||||
*/
|
||||
List<IotThinkModelFunctionDO> getThinkModelFunctionListByProductKey(String productKey);
|
||||
}
|
@@ -15,10 +15,14 @@ import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingMode
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.convert.thinkmodelfunction.IotThinkModelFunctionConvert;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotAccessModeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
|
||||
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;
|
||||
@@ -45,6 +49,11 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
|
||||
@Resource
|
||||
private IotThinkModelFunctionMapper thinkModelFunctionMapper;
|
||||
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
@Resource
|
||||
private IotSuperTableService dbStructureDataService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createThinkModelFunction(IotThinkModelFunctionSaveReqVO createReqVO) {
|
||||
@@ -57,17 +66,27 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
|
||||
// 3. 系统保留字段,不能用于标识符定义
|
||||
validateNotDefaultEventAndService(createReqVO.getIdentifier());
|
||||
|
||||
// 3. 插入数据库
|
||||
// 4. 校验产品状态,发布状态下,不允许新增功能
|
||||
validateProductStatus(createReqVO.getProductId());
|
||||
|
||||
// 5. 插入数据库
|
||||
IotThinkModelFunctionDO function = IotThinkModelFunctionConvert.INSTANCE.convert(createReqVO);
|
||||
thinkModelFunctionMapper.insert(function);
|
||||
|
||||
// 4. 如果创建的是属性,需要更新默认的事件和服务
|
||||
// 6. 如果创建的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(createReqVO.getType(), IotProductFunctionTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
|
||||
}
|
||||
return function.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_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
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))) {
|
||||
@@ -101,11 +120,14 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
|
||||
// 2. 校验功能标识符是否唯一
|
||||
validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
|
||||
|
||||
// 3. 更新数据库
|
||||
// 3. 校验产品状态,发布状态下,不允许操作功能
|
||||
validateProductStatus(updateReqVO.getProductId());
|
||||
|
||||
// 4. 更新数据库
|
||||
IotThinkModelFunctionDO thinkModelFunction = IotThinkModelFunctionConvert.INSTANCE.convert(updateReqVO);
|
||||
thinkModelFunctionMapper.updateById(thinkModelFunction);
|
||||
|
||||
// 4. 如果更新的是属性,需要更新默认的事件和服务
|
||||
// 5. 如果更新的是属性,需要更新默认的事件和服务
|
||||
if (Objects.equals(updateReqVO.getType(), IotProductFunctionTypeEnum.PROPERTY.getType())) {
|
||||
createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
|
||||
}
|
||||
@@ -127,6 +149,9 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
|
||||
throw exception(THINK_MODEL_FUNCTION_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 3. 校验产品状态,发布状态下,不允许操作功能
|
||||
validateProductStatus(functionDO.getProductId());
|
||||
|
||||
// 2. 删除功能
|
||||
thinkModelFunctionMapper.deleteById(id);
|
||||
|
||||
@@ -162,6 +187,23 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
|
||||
return thinkModelFunctionMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSuperTableDataModel(Long productId) {
|
||||
// 1. 查询产品
|
||||
IotProductDO product = productService.getProduct(productId);
|
||||
|
||||
// 2. 查询产品的物模型功能列表
|
||||
List<IotThinkModelFunctionDO> functionList = thinkModelFunctionMapper.selectListByProductId(productId);
|
||||
|
||||
// 3. 生成 TDengine 的数据模型
|
||||
dbStructureDataService.createSuperTableDataModel(product, functionList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotThinkModelFunctionDO> getThinkModelFunctionListByProductKey(String productKey) {
|
||||
return thinkModelFunctionMapper.selectListByProductKey(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认的事件和服务
|
||||
*/
|
||||
|
@@ -0,0 +1,101 @@
|
||||
<?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.TdEngineDDLMapper">
|
||||
|
||||
<!-- 创建数据库 -->
|
||||
<update id="createDatabase" parameterType="String">
|
||||
CREATE DATABASE IF NOT EXISTS ${dataBaseName}
|
||||
</update>
|
||||
|
||||
<!-- 创建超级表 -->
|
||||
<update id="createSuperTable">
|
||||
CREATE STABLE IF NOT EXISTS ${dataBaseName}.${superTableName}
|
||||
<foreach item="item" collection="columns" separator=","
|
||||
open="(" close=")">
|
||||
${item.fieldName} ${item.dataType}
|
||||
<if test="item.dataLength > 0">
|
||||
(${item.dataLength})
|
||||
</if>
|
||||
</foreach>
|
||||
TAGS
|
||||
<foreach item="item" collection="tags" separator=","
|
||||
open="(" close=")">
|
||||
${item.fieldName} ${item.dataType}
|
||||
<if test="item.dataLength > 0">
|
||||
(${item.dataLength})
|
||||
</if>
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<!-- 查看超级表 -->
|
||||
<select id="showSuperTables" resultType="java.util.Map">
|
||||
SHOW ${dataBaseName}.STABLES LIKE '${superTableName}'
|
||||
</select>
|
||||
|
||||
<!-- 描述超级表结构 -->
|
||||
<select id="describeSuperTable" resultType="java.util.Map">
|
||||
DESCRIBE ${dataBaseName}.${superTableName}
|
||||
</select>
|
||||
|
||||
<!-- 为超级表添加列 -->
|
||||
<update id="addColumnForSuperTable">
|
||||
ALTER STABLE ${dataBaseName}.${superTableName} ADD COLUMN ${column.fieldName} ${column.dataType}
|
||||
<if test="column.dataLength > 0">
|
||||
(${column.dataLength})
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<!-- 为超级表删除列 -->
|
||||
<update id="dropColumnForSuperTable">
|
||||
ALTER STABLE ${dataBaseName}.${superTableName} DROP COLUMN ${column.fieldName}
|
||||
</update>
|
||||
|
||||
<!-- 修改列宽 -->
|
||||
<update id="modifyColumnWidthForSuperTable">
|
||||
ALTER STABLE ${dataBaseName}.${superTableName} MODIFY COLUMN ${column.fieldName} ${column.dataType}
|
||||
<if test="column.dataLength > 0">
|
||||
(${column.dataLength})
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<!-- 为超级表添加标签 -->
|
||||
<update id="addTagForSuperTable">
|
||||
ALTER STABLE ${dataBaseName}.${superTableName} ADD TAG ${tag.fieldName} ${tag.dataType}
|
||||
<if test="tag.dataLength > 0">
|
||||
(${tag.dataLength})
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<!-- 为超级表删除标签 -->
|
||||
<update id="dropTagForSuperTable">
|
||||
ALTER STABLE ${dataBaseName}.${superTableName} DROP TAG ${tag.fieldName}
|
||||
</update>
|
||||
|
||||
<!-- 创建子表 -->
|
||||
<update id="createTable">
|
||||
CREATE TABLE IF NOT EXISTS ${dataBaseName}.${tableName}
|
||||
USING ${dataBaseName}.${superTableName}
|
||||
TAGS
|
||||
<foreach item="item" collection="tags" separator=","
|
||||
open="(" close=")">
|
||||
#{item.fieldValue}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<!-- 创建子表,带有 TAGS -->
|
||||
<update id="createTableWithTags">
|
||||
CREATE TABLE IF NOT EXISTS ${dataBaseName}.${tableName}
|
||||
USING ${dataBaseName}.${superTableName}
|
||||
<foreach item="item" collection="tags" separator="," open="(" close=")">
|
||||
#{item.fieldName}
|
||||
</foreach>
|
||||
TAGS
|
||||
<foreach item="item" collection="tags" separator=","
|
||||
open="(" close=")">
|
||||
#{item.fieldValue}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
</mapper>
|
@@ -0,0 +1,86 @@
|
||||
<?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.TdEngineDMLMapper">
|
||||
|
||||
<!-- 插入数据 -->
|
||||
<insert id="insertData">
|
||||
INSERT INTO ${dataBaseName}.${tableName}
|
||||
<foreach item="item" collection="columns" separator=","
|
||||
open="(" close=")">
|
||||
${item.fieldName}
|
||||
</foreach>
|
||||
VALUES
|
||||
<foreach item="item" collection="columns" separator=","
|
||||
open="(" close=")">
|
||||
#{item.fieldValue}
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 根据时间戳查询数据 -->
|
||||
<select id="selectByTimestamp" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO"
|
||||
resultType="Map">
|
||||
SELECT * FROM ${dataBaseName}.${tableName}
|
||||
WHERE ${fieldName} BETWEEN #{startTime} AND #{endTime}
|
||||
</select>
|
||||
|
||||
<!-- 获取时间范围内的数据条数 -->
|
||||
<select id="selectCountByTimestamp" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO"
|
||||
resultType="java.util.Map">
|
||||
SELECT COUNT(0) AS count
|
||||
FROM ${dataBaseName}.${tableName}
|
||||
WHERE ${fieldName} BETWEEN #{startTime} AND #{endTime}
|
||||
</select>
|
||||
|
||||
<!-- 获取最新数据 -->
|
||||
<select id="selectOneLastData" resultType="java.util.Map">
|
||||
SELECT LAST(time), *
|
||||
FROM ${tableName}
|
||||
WHERE device_id = #{deviceId}
|
||||
</select>
|
||||
|
||||
<!-- 根据标签获取最新数据 -->
|
||||
<select id="selectLastDataListByTags" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TagsSelectDO"
|
||||
resultType="Map">
|
||||
SELECT LAST(*)
|
||||
FROM ${dataBaseName}.${stableName}
|
||||
GROUP BY ${tagsName}
|
||||
</select>
|
||||
|
||||
<!-- 获取历史数据 -->
|
||||
<select id="selectHistoryDataList" resultType="java.util.Map"
|
||||
parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO">
|
||||
SELECT ${fieldName} AS data, time
|
||||
FROM ${dataBaseName}.${tableName}
|
||||
WHERE time BETWEEN #{startTime} AND #{endTime}
|
||||
AND ${fieldName} IS NOT NULL
|
||||
ORDER BY time DESC
|
||||
LIMIT #{params.rows} OFFSET #{params.page}
|
||||
</select>
|
||||
|
||||
<!-- 获取实时数据 -->
|
||||
<select id="selectRealtimeDataList" resultType="java.util.Map"
|
||||
parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO">
|
||||
SELECT ${fieldName}, time
|
||||
FROM ${dataBaseName}.${tableName}
|
||||
</select>
|
||||
|
||||
<!-- 获取聚合数据 -->
|
||||
<select id="selectAggregateDataList" resultType="java.util.Map"
|
||||
parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO">
|
||||
SELECT ${aggregate}(${fieldName})
|
||||
FROM ${dataBaseName}.${tableName}
|
||||
WHERE ts BETWEEN #{startTime} AND #{endTime} INTERVAL (${interval})
|
||||
LIMIT #{num}
|
||||
</select>
|
||||
|
||||
<!-- 获取历史数据条数 -->
|
||||
<select id="selectHistoryCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(time)
|
||||
FROM ${dataBaseName}.${tableName}
|
||||
WHERE time BETWEEN #{startTime} AND #{endTime}
|
||||
AND ${fieldName} IS NOT NULL
|
||||
</select>
|
||||
|
||||
</mapper>
|
@@ -44,7 +44,7 @@ public class PayNotifyLogDO extends BaseDO {
|
||||
/**
|
||||
* 支付通知状态
|
||||
*
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
* 枚举 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
|
@@ -52,7 +52,7 @@ public class PayNotifyTaskDO extends TenantBaseDO {
|
||||
/**
|
||||
* 通知类型
|
||||
*
|
||||
* 外键 {@link PayNotifyTypeEnum}
|
||||
* 枚举 {@link PayNotifyTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public class PayNotifyTaskDO extends TenantBaseDO {
|
||||
/**
|
||||
* 通知状态
|
||||
*
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
* 枚举 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
|
Reference in New Issue
Block a user