Commit d7b8cf547f4d96b49c3371fbbf4535a1c4dfab3e
1 parent
624f5283
【新增功能】 设备历史数据展示
Showing
8 changed files
with
112 additions
and
26 deletions
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; |
| 4 | 4 | |
| 5 | 5 | import java.util.Map; |
| 6 | 6 | |
| 7 | -/** | |
| 8 | - * @ClassDescription: 查询可视化所需入参对象 | |
| 9 | - * @ClassName: SelectDto | |
| 10 | - * @Author: andyz | |
| 11 | - * @Date: 2022-07-29 14:12:26 | |
| 12 | - * @Version 1.0 | |
| 13 | - */ | |
| 14 | 7 | @Data |
| 15 | 8 | public class SelectVisualDto { |
| 16 | 9 | |
| 17 | -// @NotBlank(message = "invalid operation: tableName can not be empty") | |
| 10 | + /** | |
| 11 | + * 数据库名称 | |
| 12 | + */ | |
| 18 | 13 | private String dataBaseName; |
| 19 | 14 | |
| 20 | -// @NotBlank(message = "invalid operation: tableName can not be empty") | |
| 15 | + /** | |
| 16 | + * 表名 | |
| 17 | + */ | |
| 21 | 18 | private String tableName; |
| 22 | 19 | |
| 23 | -// @NotBlank(message = "invalid operation: fieldName can not be empty") //属性 | |
| 20 | + /** | |
| 21 | + * 属性 | |
| 22 | + */ | |
| 24 | 23 | private String fieldName; |
| 24 | + | |
| 25 | 25 | /** |
| 26 | 26 | * 查询类型,0历史数据,1实时数据,2聚合数据 |
| 27 | 27 | */ |
| ... | ... | @@ -39,10 +39,15 @@ public class SelectVisualDto { |
| 39 | 39 | * 比如1s,1m,1h,1d代表1秒,1分钟,1小时,1天 |
| 40 | 40 | */ |
| 41 | 41 | private String interval; |
| 42 | - // @NotNull(message = "invalid operation: startTime can not be null") | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 开始时间 | |
| 45 | + */ | |
| 43 | 46 | private Long startTime; |
| 44 | 47 | |
| 45 | - // @NotNull(message = "invalid operation: endTime can not be null") | |
| 48 | + /** | |
| 49 | + * 结束时间 | |
| 50 | + */ | |
| 46 | 51 | private Long endTime; |
| 47 | 52 | |
| 48 | 53 | /** | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.controller.admin.device; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 5 | 6 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO; |
| 7 | 8 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; |
| 9 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO; | |
| 8 | 10 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService; |
| 9 | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 10 | 12 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -16,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping; |
| 16 | 18 | import org.springframework.web.bind.annotation.RestController; |
| 17 | 19 | |
| 18 | 20 | import java.util.List; |
| 21 | +import java.util.Map; | |
| 19 | 22 | |
| 20 | 23 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 21 | 24 | |
| ... | ... | @@ -35,4 +38,11 @@ public class IotDeviceDataController { |
| 35 | 38 | return success(BeanUtils.toBean(list, IotDeviceDataRespVO.class)); |
| 36 | 39 | } |
| 37 | 40 | |
| 41 | + @GetMapping("/history-data") | |
| 42 | + @Operation(summary = "获取设备属性历史数据") | |
| 43 | + public CommonResult<PageResult<IotTimeDataRespVO>> getDevicePropertiesHistoryData(@Valid IotDeviceDataReqVO deviceDataReqVO) { | |
| 44 | + PageResult<Map<String, Object>> list = deviceDataService.getDevicePropertiesHistoryData(deviceDataReqVO); | |
| 45 | + return success(BeanUtils.toBean(list, IotTimeDataRespVO.class)); | |
| 46 | + } | |
| 47 | + | |
| 38 | 48 | } |
| 39 | 49 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | +import jakarta.validation.constraints.Size; | |
| 4 | 6 | import lombok.Data; |
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | |
| 5 | 8 | |
| 6 | 9 | import java.time.LocalDateTime; |
| 7 | 10 | |
| 11 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 12 | + | |
| 8 | 13 | @Schema(description = "管理后台 - IoT 设备数据 Request VO") |
| 9 | 14 | @Data |
| 10 | -public class IotDeviceDataReqVO { | |
| 15 | +public class IotDeviceDataReqVO extends PageParam { | |
| 11 | 16 | |
| 12 | 17 | @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177") |
| 13 | 18 | private Long deviceId; |
| ... | ... | @@ -18,4 +23,9 @@ public class IotDeviceDataReqVO { |
| 18 | 23 | @Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED) |
| 19 | 24 | private String name; |
| 20 | 25 | |
| 26 | + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED) | |
| 27 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 28 | + @Size(min = 2, max = 2, message = "请选择时间范围") | |
| 29 | + private LocalDateTime[] times; | |
| 30 | + | |
| 21 | 31 | } |
| 22 | 32 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TimeData.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotTimeDataRespVO.java
| 1 | -package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine; | |
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; | |
| 2 | 2 | |
| 3 | 3 | import lombok.AllArgsConstructor; |
| 4 | 4 | import lombok.Data; |
| 5 | 5 | import lombok.NoArgsConstructor; |
| 6 | 6 | |
| 7 | -/** | |
| 8 | - * 统计的时间数据 | |
| 9 | - */ | |
| 7 | + | |
| 10 | 8 | @Data |
| 11 | 9 | @NoArgsConstructor |
| 12 | 10 | @AllArgsConstructor |
| 13 | -public class TimeData { | |
| 11 | +public class IotTimeDataRespVO { | |
| 14 | 12 | |
| 15 | 13 | /** |
| 16 | 14 | * 时间 | ... | ... |
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 { |
| 88 | 88 | |
| 89 | 89 | /** |
| 90 | 90 | * 创建表 - 创建超级表的子表 |
| 91 | + * | |
| 91 | 92 | * @param tableDO 表信息 |
| 92 | 93 | */ |
| 93 | 94 | @InterceptorIgnore(tenantLine = "true") |
| ... | ... | @@ -114,6 +115,7 @@ public interface TdEngineMapper { |
| 114 | 115 | |
| 115 | 116 | Map<String, Object> getLastData(SelectDto selectDto); |
| 116 | 117 | |
| 118 | + @InterceptorIgnore(tenantLine = "true") | |
| 117 | 119 | List<Map<String, Object>> getHistoryData(SelectVisualDto selectVisualDto); |
| 118 | 120 | |
| 119 | 121 | List<Map<String, Object>> getRealtimeData(SelectVisualDto selectVisualDto); |
| ... | ... | @@ -122,5 +124,6 @@ public interface TdEngineMapper { |
| 122 | 124 | |
| 123 | 125 | List<Map<String, Object>> getLastDataByTags(TagsSelectDao tagsSelectDao); |
| 124 | 126 | |
| 125 | - | |
| 126 | -} | |
| 127 | + @InterceptorIgnore(tenantLine = "true") | |
| 128 | + Long getHistoryCount(SelectVisualDto selectVisualDto); | |
| 129 | +} | |
| 127 | 130 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.service.device; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO; |
| 4 | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; |
| 5 | 6 | import jakarta.validation.Valid; |
| 6 | 7 | |
| 7 | 8 | import java.util.List; |
| 9 | +import java.util.Map; | |
| 8 | 10 | |
| 9 | 11 | /** |
| 10 | 12 | * IoT 设备数据 Service 接口 |
| ... | ... | @@ -30,4 +32,12 @@ public interface IotDeviceDataService { |
| 30 | 32 | * @return 设备属性最新数据 |
| 31 | 33 | */ |
| 32 | 34 | List<IotDeviceDataDO> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceId); |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 获得设备属性历史数据 | |
| 38 | + * | |
| 39 | + * @param deviceDataReqVO 设备属性历史数据 Request VO | |
| 40 | + * @return 设备属性历史数据 | |
| 41 | + */ | |
| 42 | + PageResult<Map<String, Object>> getDevicePropertiesHistoryData(@Valid IotDeviceDataReqVO deviceDataReqVO); | |
| 33 | 43 | } |
| 34 | 44 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.service.device; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.date.DateUtil; | |
| 3 | 4 | import cn.hutool.json.JSONObject; |
| 5 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | 6 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO; |
| 5 | 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 6 | 8 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; |
| 7 | 9 | import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; |
| 8 | 10 | import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; |
| 9 | 11 | import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; |
| 12 | +import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineMapper; | |
| 13 | +import cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto; | |
| 10 | 14 | import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum; |
| 11 | 15 | import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService; |
| 12 | 16 | import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService; |
| 13 | 17 | import jakarta.annotation.Resource; |
| 14 | 18 | import jakarta.validation.Valid; |
| 15 | 19 | import lombok.extern.slf4j.Slf4j; |
| 20 | +import org.springframework.beans.factory.annotation.Value; | |
| 16 | 21 | import org.springframework.stereotype.Service; |
| 17 | 22 | |
| 23 | +import java.time.ZoneId; | |
| 18 | 24 | import java.util.ArrayList; |
| 25 | +import java.util.HashMap; | |
| 19 | 26 | import java.util.List; |
| 27 | +import java.util.Map; | |
| 20 | 28 | |
| 21 | 29 | @Slf4j |
| 22 | 30 | @Service |
| 23 | 31 | public class IotDeviceDataServiceImpl implements IotDeviceDataService { |
| 24 | 32 | |
| 33 | + @Value("${spring.datasource.dynamic.datasource.tdengine.url}") | |
| 34 | + private String url; | |
| 35 | + | |
| 25 | 36 | @Resource |
| 26 | 37 | private IotDeviceService deviceService; |
| 27 | 38 | @Resource |
| ... | ... | @@ -32,6 +43,9 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService { |
| 32 | 43 | @Resource |
| 33 | 44 | private DeviceDataRedisDAO deviceDataRedisDAO; |
| 34 | 45 | |
| 46 | + @Resource | |
| 47 | + private TdEngineMapper tdEngineMapper; | |
| 48 | + | |
| 35 | 49 | @Override |
| 36 | 50 | public void saveDeviceData(String productKey, String deviceName, String message) { |
| 37 | 51 | // 1. 根据产品 key 和设备名称,获得设备信息 |
| ... | ... | @@ -91,4 +105,33 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService { |
| 91 | 105 | }); |
| 92 | 106 | return list; |
| 93 | 107 | } |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public PageResult<Map<String, Object>> getDevicePropertiesHistoryData(IotDeviceDataReqVO deviceDataReqVO) { | |
| 111 | + PageResult<Map<String, Object>> pageResult = new PageResult<>(); | |
| 112 | + // 1. 获取设备信息 | |
| 113 | + IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId()); | |
| 114 | + // 2. 获取设备属性历史数据 | |
| 115 | + SelectVisualDto selectVisualDto = new SelectVisualDto(); | |
| 116 | + selectVisualDto.setDataBaseName(getDatabaseName()); | |
| 117 | + selectVisualDto.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName())); | |
| 118 | + selectVisualDto.setFieldName(deviceDataReqVO.getIdentifier()); | |
| 119 | + selectVisualDto.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime()); | |
| 120 | + selectVisualDto.setEndTime(DateUtil.date(deviceDataReqVO.getTimes()[1].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime()); | |
| 121 | + Map<String, Object> params = new HashMap<>(); | |
| 122 | + params.put("rows", deviceDataReqVO.getPageSize()); | |
| 123 | + params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize()); | |
| 124 | + selectVisualDto.setParams(params); | |
| 125 | + pageResult.setList(tdEngineMapper.getHistoryData(selectVisualDto)); | |
| 126 | + pageResult.setTotal(tdEngineMapper.getHistoryCount(selectVisualDto)); | |
| 127 | + return pageResult; | |
| 128 | + } | |
| 129 | + | |
| 130 | + private String getDatabaseName() { | |
| 131 | + return url.substring(url.lastIndexOf("/") + 1); | |
| 132 | + } | |
| 133 | + | |
| 134 | + private static String getDeviceTableName(String productKey, String deviceName) { | |
| 135 | + return String.format("device_%s_%s", productKey.toLowerCase(), deviceName.toLowerCase()); | |
| 136 | + } | |
| 94 | 137 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml
| ... | ... | @@ -292,17 +292,18 @@ |
| 292 | 292 | |
| 293 | 293 | <select id="getHistoryData" resultType="java.util.Map" |
| 294 | 294 | parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto"> |
| 295 | - SELECT #{fieldName}, ts | |
| 296 | - FROM #{dataBaseName}.#{tableName} | |
| 297 | - WHERE ts BETWEEN #{startTime} AND #{endTime} | |
| 298 | - LIMIT #{num} | |
| 295 | + SELECT ${fieldName} as data, time | |
| 296 | + FROM ${dataBaseName}.${tableName} | |
| 297 | + WHERE time BETWEEN #{startTime} AND #{endTime} | |
| 298 | + AND ${fieldName} IS NOT NULL | |
| 299 | + ORDER BY time DESC | |
| 300 | + LIMIT #{params.rows} offset #{params.page} | |
| 299 | 301 | </select> |
| 300 | 302 | |
| 301 | 303 | <select id="getRealtimeData" resultType="java.util.Map" |
| 302 | 304 | parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto"> |
| 303 | - SELECT #{fieldName}, ts | |
| 305 | + SELECT #{fieldName}, time | |
| 304 | 306 | FROM #{dataBaseName}.#{tableName} |
| 305 | - LIMIT #{num} | |
| 306 | 307 | </select> |
| 307 | 308 | |
| 308 | 309 | <select id="getAggregateData" resultType="java.util.Map" |
| ... | ... | @@ -316,5 +317,11 @@ |
| 316 | 317 | <select id="describeSuperTable" resultType="java.util.Map"> |
| 317 | 318 | DESCRIBE ${dataBaseName}.${superTableName} |
| 318 | 319 | </select> |
| 320 | + <select id="getHistoryCount" resultType="java.lang.Long"> | |
| 321 | + SELECT count(time) | |
| 322 | + FROM ${dataBaseName}.${tableName} | |
| 323 | + WHERE time BETWEEN #{startTime} AND #{endTime} | |
| 324 | + AND ${fieldName} IS NOT NULL | |
| 325 | + </select> | |
| 319 | 326 | |
| 320 | 327 | </mapper> | ... | ... |