20260412:激活时间改为设备数据最近上报时间

This commit is contained in:
George 2026-04-12 09:09:27 +08:00
parent a2049b0d60
commit c38fa32986
7 changed files with 114 additions and 6 deletions

View File

@ -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<ValueItem> getCacheDeviceStatus(Long productId, String deviceNumber);
/**
* 仅从 Redis 读取物模型哈希各点位中的上报时间 ts,取最大值。
* 不初始化空缓存;无 key 或无任何 ts 时返回 null。
*/
Date getLatestValueTimestamp(Long productId, String deviceNumber);
/**
* 缓存设备物模型值到redis

View File

@ -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<String, String> 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

View File

@ -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;
}

View File

@ -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<DeviceShortOutput> 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<SubDeviceListVO> 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<DeviceShortOutput> listTerminalUser(Device device) {
return deviceMapper.listTerminalUser(device);
List<DeviceShortOutput> list = deviceMapper.listTerminalUser(device);
fillShortListLatestReportTime(list);
return list;
}
@Override

View File

@ -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",

View File

@ -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": "删除",

View File

@ -182,9 +182,9 @@
<el-tag size="mini" type="info">Ver {{ scope.row.firmwareVersion }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('device.index.105953-32')" align="center" prop="activeTime" width="120">
<el-table-column :label="$t('device.index.105953-32')" align="center" prop="updateTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.activeTime, '{y}-{m}-{d}') }}</span>
<span>{{ scope.row.updateTime ? parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('device.index.105953-33')" align="center" prop="createTime" width="120">
@ -295,7 +295,7 @@
</el-tooltip>
</el-descriptions-item>
<el-descriptions-item :label="$t('device.index.105953-32')">
{{ parseTime(item.activeTime, '{y}-{m}-{d}') }}
{{ item.updateTime ? parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</el-descriptions-item>
</el-descriptions>
</el-col>