From c38fa32986a6739fbe7c9ca37d284408f80917bb Mon Sep 17 00:00:00 2001 From: George Date: Sun, 12 Apr 2026 09:09:27 +0800 Subject: [PATCH] =?UTF-8?q?20260412:=E6=BF=80=E6=B4=BB=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE=E6=9C=80?= =?UTF-8?q?=E8=BF=91=E4=B8=8A=E6=8A=A5=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fastbee/iot/cache/ITSLValueCache.java | 6 ++ .../iot/cache/impl/TSLValueCacheImpl.java | 29 +++++++++ .../fastbee/iot/model/DeviceShortOutput.java | 13 ++++ .../iot/service/impl/DeviceServiceImpl.java | 62 ++++++++++++++++++- yack-web/src/lang/en-US/device.json | 2 +- yack-web/src/lang/zh-CN/device.json | 2 +- yack-web/src/views/iot/device/index.vue | 6 +- 7 files changed, 114 insertions(+), 6 deletions(-) diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/ITSLValueCache.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/ITSLValueCache.java index aec60571..085d225d 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/ITSLValueCache.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/ITSLValueCache.java @@ -2,6 +2,7 @@ package com.fastbee.iot.cache; import com.fastbee.iot.model.ThingsModels.ValueItem; +import java.util.Date; import java.util.List; /** @@ -20,6 +21,11 @@ public interface ITSLValueCache { */ public List getCacheDeviceStatus(Long productId, String deviceNumber); + /** + * 仅从 Redis 读取物模型哈希各点位中的上报时间 ts,取最大值。 + * 不初始化空缓存;无 key 或无任何 ts 时返回 null。 + */ + Date getLatestValueTimestamp(Long productId, String deviceNumber); /** * 缓存设备物模型值到redis diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/impl/TSLValueCacheImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/impl/TSLValueCacheImpl.java index 20100f7c..3b3d227b 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/impl/TSLValueCacheImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/cache/impl/TSLValueCacheImpl.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -55,6 +56,34 @@ public class TSLValueCacheImpl implements ITSLValueCache { return valueList; } + @Override + public Date getLatestValueTimestamp(Long productId, String deviceNumber) { + if (productId == null || deviceNumber == null || deviceNumber.isEmpty()) { + return null; + } + String key = RedisKeyBuilder.buildTSLVCacheKey(productId, deviceNumber); + Map map = redisCache.hashEntity(key); + if (map == null || map.isEmpty()) { + return null; + } + Date max = null; + for (String s : map.values()) { + if (s == null || s.isEmpty()) { + continue; + } + try { + ValueItem vi = JSONObject.parseObject(s, ValueItem.class); + if (vi != null && vi.getTs() != null) { + if (max == null || vi.getTs().after(max)) { + max = vi.getTs(); + } + } + } catch (Exception ignored) { + //单字段解析失败跳过 + } + } + return max; + } /** * 缓存设备物模型值到redis diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java index 2fd4cb8e..606d9eb7 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java @@ -100,6 +100,11 @@ public class DeviceShortOutput { @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd") private Date createTime; + /** + * 物模型 Redis 缓存中各点位上报时间 ts 的最大值(shortList 接口填充);无上报时为 null + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; @Excel(name = "网关设备编号(子设备使用)") private String gwDevCode; /** @@ -214,6 +219,14 @@ public class DeviceShortOutput { this.createTime = createTime; } + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + public String getGwDevCode() { return gwDevCode; } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java index 9c1af438..505f2b63 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -859,9 +859,67 @@ public class DeviceServiceImpl implements IDeviceService { .forEach(it -> it.setAlertCount(item)); } } + fillShortListLatestReportTime(list); return list; } + /** + * 短时列表:从 Redis 物模型缓存聚合各点位 ts最大值写入 updateTime(方案 B)。 + * 网关设备自身往往无属性上报,数据在子设备 SN 的缓存中,需合并子设备的 ts。 + */ + private void fillShortListLatestReportTime(List list) { + if (CollectionUtils.isEmpty(list)) { + return; + } + for (DeviceShortOutput row : list) { + if (row.getProductId() == null || StringUtils.isEmpty(row.getSerialNumber())) { + continue; + } + Date latest = itslValueCache.getLatestValueTimestamp(row.getProductId(), row.getSerialNumber()); + if (shouldMergeSubDevicesLatestReport(row)) { + Date subMax = maxLatestReportAmongSubDevices(row.getDeviceId()); + if (subMax != null && (latest == null || subMax.after(latest))) { + latest = subMax; + } + } + if (latest == null) { + row.setUpdateTime(row.getActiveTime()); + } else { + row.setUpdateTime(latest); + } + } + } + + private boolean shouldMergeSubDevicesLatestReport(DeviceShortOutput row) { + if (row.getDeviceId() == null) { + return false; + } + if (row.getDeviceType() != null && row.getDeviceType() == DeviceType.GATEWAY.getCode()) { + return true; + } + return row.getSubDeviceCount() != null && row.getSubDeviceCount() > 0; + } + + private Date maxLatestReportAmongSubDevices(Long gwDeviceId) { + SubGateway subQ = new SubGateway(); + subQ.setGwDeviceId(gwDeviceId); + List subs = subGatewayMapper.selectGatewayList(subQ); + if (CollectionUtils.isEmpty(subs)) { + return null; + } + Date max = null; + for (SubDeviceListVO sub : subs) { + if (sub.getSubProductId() == null || StringUtils.isEmpty(sub.getSubDeviceNo())) { + continue; + } + Date t = itslValueCache.getLatestValueTimestamp(sub.getSubProductId(), sub.getSubDeviceNo()); + if (t != null && (max == null || t.after(max))) { + max = t; + } + } + return max; + } + /** * 查询设备 * @@ -1920,7 +1978,9 @@ public class DeviceServiceImpl implements IDeviceService { @Override public List listTerminalUser(Device device) { - return deviceMapper.listTerminalUser(device); + List list = deviceMapper.listTerminalUser(device); + fillShortListLatestReportTime(list); + return list; } @Override diff --git a/yack-web/src/lang/en-US/device.json b/yack-web/src/lang/en-US/device.json index f8a01da6..b2fb6b2f 100644 --- a/yack-web/src/lang/en-US/device.json +++ b/yack-web/src/lang/en-US/device.json @@ -454,7 +454,7 @@ "device.index.105953-29": "signal", "device.index.105953-30": "Positioning method", "device.index.105953-31": "Firmware version", - "device.index.105953-32": "Activation time", + "device.index.105953-32": "Last update", "device.index.105953-33": "Creation time", "device.index.105953-34": "operation", "device.index.105953-35": "delete", diff --git a/yack-web/src/lang/zh-CN/device.json b/yack-web/src/lang/zh-CN/device.json index 11ab66a1..18bfe6a0 100644 --- a/yack-web/src/lang/zh-CN/device.json +++ b/yack-web/src/lang/zh-CN/device.json @@ -454,7 +454,7 @@ "device.index.105953-29": "信号", "device.index.105953-30": "定位方式", "device.index.105953-31": "固件版本", - "device.index.105953-32": "激活时间", + "device.index.105953-32": "更新时间", "device.index.105953-33": "创建时间", "device.index.105953-34": "操作", "device.index.105953-35": "删除", diff --git a/yack-web/src/views/iot/device/index.vue b/yack-web/src/views/iot/device/index.vue index 220c8de0..d229ddf2 100644 --- a/yack-web/src/views/iot/device/index.vue +++ b/yack-web/src/views/iot/device/index.vue @@ -182,9 +182,9 @@ Ver {{ scope.row.firmwareVersion }} - + @@ -295,7 +295,7 @@ - {{ parseTime(item.activeTime, '{y}-{m}-{d}') }} + {{ item.updateTime ? parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}