背景:此前「滞后几天」在后端(calculate_offset)和前端(Dashboard.vue 自算
diffDays/diffHours)各有一套实现,阈值与粒度都不同,跨日必然打架 —— 昨天
23:00 的数据在次日 01:00 查看,后端说「滞后 1 天」,前端标「昨日数据」。
services/time_utils.py:
- 新增 staleness_of(),统一返回 {level, days, text},level 分
unknown / ok / yesterday / lagging / severe 五级。
- 日期解析改用正则抠 YYYY-MM-DD,分隔符兼容 - / _,因此 ISO 的
2026-09-15T08:30:00Z、2026/09/15、2026_09_15 都能识别。
原来的 split(' ')[0] + strptime('%Y-%m-%d') 遇到 ISO T 或斜杠会直接返回
「时间解析失败」,而 82 爬虫的 target_time 是原样透传接口返回值的,格式
不可控。
- calculate_offset 保留为兼容入口,内部委托 staleness_of。
models.py:
- to_dict() 新增 stale_level / stale_days。
- 修复状态映射:白名单原来只有 ['离线','异常','已离线'],漏了 'offline',
而 /add_device 创建手动设备写的正是 status='offline',导致手动添加的设备
被映射成 online、在面板上永远显示「在线」。改为大小写归一后比对,并纳入
unknown/未知。
实测:本地库 status 分布由 online:103/offline:2 修正为 online:99/offline:6。
106 lines
4.1 KiB
Python
106 lines
4.1 KiB
Python
# models.py
|
||
from datetime import datetime
|
||
from extensions import db
|
||
# services.time_utils 只依赖 re/datetime,不会与 extensions/models 形成循环导入
|
||
from services.time_utils import staleness_of
|
||
|
||
|
||
class Device(db.Model):
|
||
__tablename__ = 'devices'
|
||
|
||
id = db.Column(db.Integer, primary_key=True)
|
||
name = db.Column(db.String(100), unique=True, index=True)
|
||
source = db.Column(db.String(50))
|
||
|
||
# 快照字段(爬虫更新)
|
||
status = db.Column(db.String(50))
|
||
current_value = db.Column(db.String(200))
|
||
latest_time = db.Column(db.String(50))
|
||
json_data = db.Column(db.Text)
|
||
check_time = db.Column(db.String(50))
|
||
reason = db.Column(db.String(255))
|
||
offset = db.Column(db.String(50))
|
||
|
||
# ✅ 新增字段:文件数量
|
||
file_count = db.Column(db.Integer, default=0)
|
||
|
||
# 手动录入字段(受保护,run_monitor 不主动覆盖)
|
||
install_site = db.Column(db.String(100), default="")
|
||
is_maintaining = db.Column(db.Boolean, default=False)
|
||
is_hidden = db.Column(db.Boolean, default=False)
|
||
|
||
# 白名单字段 (根据上下文可能存在,补全以防万一)
|
||
is_whitelist = db.Column(db.Boolean, default=False)
|
||
|
||
def to_dict(self):
|
||
# 状态归一:中英文各种写法都要覆盖。
|
||
# 注意 'offline' 是 /add_device 创建手动设备时写入的值,原先的白名单
|
||
# 漏了它,导致手动添加的设备被映射成 online、在面板上永远显示“在线”。
|
||
raw_status = str(self.status or '').strip().lower()
|
||
is_offline = raw_status in (
|
||
'离线', '异常', '已离线', 'offline', 'unknown', '未知'
|
||
)
|
||
api_status = 'offline' if is_offline else 'online'
|
||
|
||
# 数据时效在读取时实时计算,不读 offset 列。
|
||
# 该列是写入时刻的快照,采集一停摆就整体腐烂(库里停在 2026-02-06,
|
||
# offset 却仍显示“当天”)。列保留但已废弃,见 services/db_ingest.py。
|
||
stale = staleness_of(self.latest_time)
|
||
|
||
return {
|
||
'id': self.id,
|
||
'name': self.name,
|
||
'source': self.source,
|
||
'latest_time': self.latest_time,
|
||
'status': api_status,
|
||
'status_text': self.status,
|
||
'value': self.current_value,
|
||
'reason': self.reason,
|
||
'install_site': self.install_site or '',
|
||
'is_maintaining': self.is_maintaining,
|
||
'is_hidden': self.is_hidden,
|
||
'is_whitelist': self.is_whitelist,
|
||
# 时效三件套:前端不再自己算滞后天数,统一消费这三个字段
|
||
'offset': stale['text'], # 中文描述,兼容旧调用方
|
||
'stale_level': stale['level'], # unknown/ok/yesterday/lagging/severe
|
||
'stale_days': stale['days'], # 滞后自然日数,unknown 时为 None
|
||
'file_count': self.file_count # ✅ 返回给前端
|
||
}
|
||
|
||
|
||
class DeviceHistory(db.Model):
|
||
__tablename__ = 'device_history'
|
||
|
||
id = db.Column(db.Integer, primary_key=True)
|
||
device_id = db.Column(db.Integer, db.ForeignKey('devices.id'))
|
||
|
||
data_time = db.Column(db.String(50))
|
||
status = db.Column(db.String(50))
|
||
result_data = db.Column(db.String(200), default="")
|
||
json_data = db.Column(db.Text)
|
||
file_path = db.Column(db.String(255))
|
||
|
||
# ✅ 新增字段:历史记录文件数量
|
||
file_count = db.Column(db.Integer, default=0)
|
||
|
||
recorded_at = db.Column(db.DateTime, default=datetime.now)
|
||
|
||
|
||
class MaintenanceLog(db.Model):
|
||
__tablename__ = 'maintenance_logs'
|
||
id = db.Column(db.Integer, primary_key=True)
|
||
device_name = db.Column(db.String(100), nullable=False)
|
||
engineer = db.Column(db.String(50))
|
||
location = db.Column(db.String(100))
|
||
content = db.Column(db.Text)
|
||
timestamp = db.Column(db.DateTime, default=datetime.now)
|
||
|
||
def to_dict(self):
|
||
return {
|
||
'id': self.id,
|
||
'device_name': self.device_name,
|
||
'engineer': self.engineer or '',
|
||
'location': self.location or '',
|
||
'content': self.content,
|
||
'timestamp': self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||
} |