fix: 统一数据库时间为北京时间(UTC+8)

- 新增 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 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 16:23:05 +08:00
parent 24c94ccfbe
commit b1b96a3ebc
2 changed files with 41 additions and 19 deletions

View File

@@ -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)
# ==================== 数据库管理 ====================

20
app/utils/timezone.py Normal file
View File

@@ -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)