소스 검색

【新增功能】 设备历史数据展示

安浩浩 9 달 전
부모
커밋
d7b8cf547f

+ 17 - 12
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/visual/SelectVisualDto.java

@@ -4,24 +4,24 @@ import lombok.Data;
 
 import java.util.Map;
 
-/**
- * @ClassDescription: 查询可视化所需入参对象
- * @ClassName: SelectDto
- * @Author: andyz
- * @Date: 2022-07-29 14:12:26
- * @Version 1.0
- */
 @Data
 public class SelectVisualDto {
 
-//    @NotBlank(message = "invalid operation: tableName can not be empty")
+    /**
+     * 数据库名称
+     */
     private String dataBaseName;
 
-//    @NotBlank(message = "invalid operation: tableName can not be empty")
+    /**
+     * 表名
+     */
     private String tableName;
 
-//    @NotBlank(message = "invalid operation: fieldName can not be empty") //属性
+    /**
+     * 属性
+     */
     private String fieldName;
+
     /**
      * 查询类型,0历史数据,1实时数据,2聚合数据
      */
@@ -39,10 +39,15 @@ public class SelectVisualDto {
      * 比如1s,1m,1h,1d代表1秒,1分钟,1小时,1天
      */
     private String interval;
-    //    @NotNull(message = "invalid operation: startTime can not be null")
+
+    /**
+     * 开始时间
+     */
     private Long startTime;
 
-    //    @NotNull(message = "invalid operation: endTime can not be null")
+    /**
+     * 结束时间
+     */
     private Long endTime;
 
     /**

+ 10 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java

@@ -1,10 +1,12 @@
 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.IotDeviceDataReqVO;
 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;
@@ -16,6 +18,7 @@ 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;
 
@@ -35,4 +38,11 @@ public class IotDeviceDataController {
         return success(BeanUtils.toBean(list, IotDeviceDataRespVO.class));
     }
 
+    @GetMapping("/history-data")
+    @Operation(summary = "获取设备属性历史数据")
+    public CommonResult<PageResult<IotTimeDataRespVO>> getDevicePropertiesHistoryData(@Valid IotDeviceDataReqVO deviceDataReqVO) {
+        PageResult<Map<String, Object>> list = deviceDataService.getDevicePropertiesHistoryData(deviceDataReqVO);
+        return success(BeanUtils.toBean(list, IotTimeDataRespVO.class));
+    }
+
 }

+ 11 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataReqVO.java

@@ -1,13 +1,18 @@
 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 IotDeviceDataReqVO {
+public class IotDeviceDataReqVO extends PageParam {
 
     @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
     private Long deviceId;
@@ -18,4 +23,9 @@ public class IotDeviceDataReqVO {
     @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;
+
 }

+ 3 - 5
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TimeData.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotTimeDataRespVO.java

@@ -1,16 +1,14 @@
-package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+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 TimeData {
+public class IotTimeDataRespVO {
 
     /**
      * 时间

+ 5 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineMapper.java

@@ -88,6 +88,7 @@ public interface TdEngineMapper {
 
     /**
      * 创建表 - 创建超级表的子表
+     *
      * @param tableDO 表信息
      */
     @InterceptorIgnore(tenantLine = "true")
@@ -114,6 +115,7 @@ public interface TdEngineMapper {
 
     Map<String, Object> getLastData(SelectDto selectDto);
 
+    @InterceptorIgnore(tenantLine = "true")
     List<Map<String, Object>> getHistoryData(SelectVisualDto selectVisualDto);
 
     List<Map<String, Object>> getRealtimeData(SelectVisualDto selectVisualDto);
@@ -122,5 +124,6 @@ public interface TdEngineMapper {
 
     List<Map<String, Object>> getLastDataByTags(TagsSelectDao tagsSelectDao);
 
-
-}
+    @InterceptorIgnore(tenantLine = "true")
+    Long getHistoryCount(SelectVisualDto selectVisualDto);
+}

+ 10 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java

@@ -1,10 +1,12 @@
 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.IotDeviceDataReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
 import jakarta.validation.Valid;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * IoT 设备数据 Service 接口
@@ -30,4 +32,12 @@ public interface IotDeviceDataService {
      * @return 设备属性最新数据
      */
     List<IotDeviceDataDO> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceId);
+
+    /**
+     * 获得设备属性历史数据
+     *
+     * @param deviceDataReqVO 设备属性历史数据 Request VO
+     * @return 设备属性历史数据
+     */
+    PageResult<Map<String, Object>> getDevicePropertiesHistoryData(@Valid IotDeviceDataReqVO deviceDataReqVO);
 }

+ 43 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java

@@ -1,27 +1,38 @@
 package cn.iocoder.yudao.module.iot.service.device;
 
+import cn.hutool.core.date.DateUtil;
 import cn.hutool.json.JSONObject;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
 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.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.TdEngineMapper;
+import cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto;
 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
@@ -32,6 +43,9 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
     @Resource
     private DeviceDataRedisDAO deviceDataRedisDAO;
 
+    @Resource
+    private TdEngineMapper tdEngineMapper;
+
     @Override
     public void saveDeviceData(String productKey, String deviceName, String message) {
         // 1. 根据产品 key 和设备名称,获得设备信息
@@ -91,4 +105,33 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
         });
         return list;
     }
+
+    @Override
+    public PageResult<Map<String, Object>> getDevicePropertiesHistoryData(IotDeviceDataReqVO deviceDataReqVO) {
+        PageResult<Map<String, Object>> pageResult = new PageResult<>();
+        // 1. 获取设备信息
+        IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
+        // 2. 获取设备属性历史数据
+        SelectVisualDto selectVisualDto = new SelectVisualDto();
+        selectVisualDto.setDataBaseName(getDatabaseName());
+        selectVisualDto.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
+        selectVisualDto.setFieldName(deviceDataReqVO.getIdentifier());
+        selectVisualDto.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
+        selectVisualDto.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());
+        selectVisualDto.setParams(params);
+        pageResult.setList(tdEngineMapper.getHistoryData(selectVisualDto));
+        pageResult.setTotal(tdEngineMapper.getHistoryCount(selectVisualDto));
+        return pageResult;
+    }
+
+    private String getDatabaseName() {
+        return url.substring(url.lastIndexOf("/") + 1);
+    }
+
+    private static String getDeviceTableName(String productKey, String deviceName) {
+        return String.format("device_%s_%s", productKey.toLowerCase(), deviceName.toLowerCase());
+    }
 }

+ 13 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml

@@ -292,17 +292,18 @@
 
     <select id="getHistoryData" resultType="java.util.Map"
             parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto">
-        SELECT #{fieldName}, ts
-        FROM #{dataBaseName}.#{tableName}
-        WHERE ts BETWEEN #{startTime} AND #{endTime}
-        LIMIT #{num}
+        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="getRealtimeData" resultType="java.util.Map"
             parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto">
-        SELECT #{fieldName}, ts
+        SELECT #{fieldName}, time
         FROM #{dataBaseName}.#{tableName}
-        LIMIT #{num}
     </select>
 
     <select id="getAggregateData" resultType="java.util.Map"
@@ -316,5 +317,11 @@
     <select id="describeSuperTable" resultType="java.util.Map">
         DESCRIBE ${dataBaseName}.${superTableName}
     </select>
+    <select id="getHistoryCount" resultType="java.lang.Long">
+        SELECT count(time)
+        FROM ${dataBaseName}.${tableName}
+        WHERE time BETWEEN #{startTime} AND #{endTime}
+          AND ${fieldName} IS NOT NULL
+    </select>
 
 </mapper>