From b1b96a3ebc0f36fdcd870eb14926f3559411fcbf Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Mon, 9 Mar 2026 16:23:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=97=B6=E9=97=B4=E4=B8=BA=E5=8C=97=E4=BA=AC=E6=97=B6?= =?UTF-8?q?=E9=97=B4=EF=BC=88UTC+8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 app/utils/timezone.py 提供 beijing_now() 工具函数 - models.py 所有表的 created_at/updated_at 默认值从 UTC 改为北京时间 - 解决 event_time(边缘端北京时间)与 handled_at(服务端UTC)差8小时的问题 Co-Authored-By: Claude Opus 4.6 --- app/models.py | 40 +++++++++++++++++++++------------------- app/utils/timezone.py | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 app/utils/timezone.py diff --git a/app/models.py b/app/models.py index 06d7467..820ad87 100644 --- a/app/models.py +++ b/app/models.py @@ -4,6 +4,8 @@ import enum import os from datetime import datetime, timezone + +from app.utils.timezone import beijing_now from typing import Optional from sqlalchemy import ( Column, String, Integer, SmallInteger, BigInteger, Boolean, Float, DateTime, Text, Enum, JSON, @@ -102,9 +104,9 @@ class Alert(Base): work_order_id = Column(Integer, ForeignKey("work_orders.id"), nullable=True) # 时间戳 - created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), - onupdate=lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime, default=lambda: beijing_now()) + updated_at = Column(DateTime, default=lambda: beijing_now(), + onupdate=lambda: beijing_now()) # 关系 work_order = relationship("WorkOrder", back_populates="alerts", foreign_keys=[work_order_id]) @@ -175,9 +177,9 @@ class WorkOrder(Base): assigned_at = Column(DateTime, nullable=True) started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) - created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), - onupdate=lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime, default=lambda: beijing_now()) + updated_at = Column(DateTime, default=lambda: beijing_now(), + onupdate=lambda: beijing_now()) # 关系 alerts = relationship("Alert", back_populates="work_order", foreign_keys=[Alert.work_order_id]) @@ -231,9 +233,9 @@ class EdgeDevice(Base): # 扩展 extra_info = Column(JSON, nullable=True) - created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), - onupdate=lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime, default=lambda: beijing_now()) + updated_at = Column(DateTime, default=lambda: beijing_now(), + onupdate=lambda: beijing_now()) def to_dict(self) -> dict: return { @@ -279,9 +281,9 @@ class AlarmEvent(Base): handler = Column(String(64), comment="处理人") handle_remark = Column(Text, comment="处理备注") handled_at = Column(DateTime, comment="处理时间") - created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), - onupdate=lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime, default=lambda: beijing_now()) + updated_at = Column(DateTime, default=lambda: beijing_now(), + onupdate=lambda: beijing_now()) __table_args__ = ( Index('idx_alarm_event_time', 'event_time'), @@ -323,7 +325,7 @@ class AlarmEventExt(Base): ext_type = Column(String(32), comment="扩展类型: EDGE/POST/MANUAL") ext_data = Column(JSON, comment="扩展数据") roi_config = Column(JSON, comment="ROI配置快照") - created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime, default=lambda: beijing_now()) def to_dict(self) -> dict: return { @@ -349,7 +351,7 @@ class AlarmLlmAnalysis(Base): risk_score = Column(Integer, comment="风险评分 0-100") confidence_score = Column(Float, comment="分析置信度") suggestion = Column(Text, comment="处置建议") - created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime, default=lambda: beijing_now()) def to_dict(self) -> dict: return { @@ -413,8 +415,8 @@ class NotifyArea(Base): area_name = Column(String(100), nullable=False) description = Column(String(200), nullable=True) enabled = Column(SmallInteger, default=1) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + created_at = Column(DateTime, default=beijing_now) + updated_at = Column(DateTime, default=beijing_now, onupdate=beijing_now) class CameraAreaBinding(Base): @@ -424,7 +426,7 @@ class CameraAreaBinding(Base): id = Column(Integer, primary_key=True, autoincrement=True) camera_id = Column(String(64), unique=True, nullable=False, index=True) area_id = Column(String(36), nullable=False, index=True) - created_at = Column(DateTime, default=datetime.now) + created_at = Column(DateTime, default=beijing_now) class AreaPersonBinding(Base): @@ -438,8 +440,8 @@ class AreaPersonBinding(Base): role = Column(String(20), default="SECURITY") notify_level = Column(Integer, default=1) enabled = Column(SmallInteger, default=1) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + created_at = Column(DateTime, default=beijing_now) + updated_at = Column(DateTime, default=beijing_now, onupdate=beijing_now) # ==================== 数据库管理 ==================== diff --git a/app/utils/timezone.py b/app/utils/timezone.py new file mode 100644 index 0000000..b8654fc --- /dev/null +++ b/app/utils/timezone.py @@ -0,0 +1,20 @@ +""" +时区工具 + +本系统所有时间统一使用北京时间(UTC+8)。 +边缘端上报的 event_time 是北京时间,服务端生成的时间也使用北京时间, +确保数据库中所有时间字段语义一致。 +""" + +from datetime import datetime, timezone, timedelta + +# 北京时区 UTC+8 +BEIJING_TZ = timezone(timedelta(hours=8)) + + +def beijing_now() -> datetime: + """返回当前北京时间(naive datetime,无时区信息) + + 用于数据库存储,与边缘端上报的 event_time 格式一致。 + """ + return datetime.now(BEIJING_TZ).replace(tzinfo=None)