问题:app.py:auto_monitor_job(定时)和 routes/api.py:run_monitor(手动) 各自维护了一份几乎相同但细节不一致的写入逻辑,同一条数据经不同入口落库后 latest_time / source / offset / file_count 可能不同。 - 新增 services/time_utils.py:calculate_offset 下沉到无依赖模块,避免 db_ingest 与 routes.api 互相 import 形成循环。routes/api.py 里 re-export 一次,保证 app.py 原有的 from routes.api import calculate_offset 不失效。 - 新增 services/db_ingest.py:ingest_device_data(),承载全部入库细节,只 add/flush 不 commit,事务边界交给调用方。 - routes/api.py:run_monitor 瘦身为「触发爬虫 -> 调管道 -> 提交」。 - models.py:to_dict() 的 offset 改为读取时用 calculate_offset(latest_time) 实时计算,不再读 offset 列。该列是写入时刻的快照,采集一停摆就整体失真 (库里停在 2026-02-06,offset 却仍显示“当天”)。列保留但已废弃, 不执行 ALTER TABLE DROP COLUMN。 入库语义(db_ingest): - 只有爬虫拿到真实业务时间才更新主表 latest_time,拿不到就保留上一个有效值 (冻结),不再回退到 current_time。 - DeviceHistory 单独用 history_time:主表回答“数据到什么时候”,历史回答 “什么时候采过”。 - 历史表 json_data 改存本次增量切片,不再复制主表那份越滚越大的累积 JSON。 - 主表 json_data 改为覆盖式更新以切断无限膨胀,但保留 APP_OWNED_KEYS (bound_iccid / is_whitelist)—— 这两个键由 /bind_device_card 和 /toggle_whitelist 写入,按原方案直接覆盖会清空所有设备-流量卡绑定。
94 lines
3.4 KiB
Python
94 lines
3.4 KiB
Python
# models.py
|
||
from datetime import datetime
|
||
from extensions import db
|
||
# services.time_utils 只依赖 datetime,不会与 extensions/models 形成循环导入
|
||
from services.time_utils import calculate_offset
|
||
|
||
|
||
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):
|
||
# 统一状态映射逻辑
|
||
api_status = 'offline' if self.status in ['离线', '异常', '已离线'] else 'online'
|
||
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,
|
||
# 滞后天数在读取时实时计算,不读 self.offset。
|
||
# offset 列是写入时算好的"化石",采集一旦停摆就会整体腐烂
|
||
# (例如库停在 2026-02-06,offset 却仍显示"当天")。
|
||
'offset': calculate_offset(self.latest_time),
|
||
'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')
|
||
} |