# backend/app/models/alert.py """ 告警规则模型 """ from sqlalchemy import Column, Integer, String, Boolean, Time, TIMESTAMP, Numeric, Text, ForeignKey from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.db.base import Base from datetime import datetime, time as dt_time from typing import Optional, List from pydantic import BaseModel, Field from enum import Enum class AlertType(str, Enum): """告警类型""" PRICE = "price" # 价格告警 CHANGE_PERCENT = "change_percent" # 涨跌幅告警 TECHNICAL = "technical" # 技术指标告警 VOLUME = "volume" # 成交量告警 class AlertOperator(str, Enum): """告警操作符""" GT = "gt" # 大于 LT = "lt" # 小于 EQ = "eq" # 等于 GE = "ge" # 大于等于 LE = "le" # 小于等于 NE = "ne" # 不等于 class NotifyChannel(str, Enum): """通知渠道""" IN_APP = "站内消息" EMAIL = "邮件" SMS = "短信" WECHAT = "企业微信" DINGTALK = "钉钉" class AlertRule(Base): """告警规则表""" __tablename__ = "alert_rule" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, nullable=False, index=True) name = Column(String(100), nullable=False, comment="告警名称") symbol = Column(String(20), nullable=False, index=True, comment="品种代码") type = Column(String(20), nullable=False, index=True, comment="告警类型") condition = Column(JSONB, nullable=False, comment="触发条件") channels = Column(JSONB, nullable=False, comment="通知渠道") enabled = Column(Boolean, default=True, index=True, comment="是否启用") start_time = Column(Time, nullable=True, comment="生效开始时间") end_time = Column(Time, nullable=True, comment="生效结束时间") repeat_interval = Column(Integer, default=0, comment="重复间隔(秒)") last_triggered_at = Column(TIMESTAMP(timezone=True), nullable=True, comment="上次触发时间") trigger_count = Column(Integer, default=0, comment="触发次数") created_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) updated_at = Column(TIMESTAMP(timezone=True), server_default=func.now(), onupdate=func.now()) # 关系 history = relationship("AlertHistory", back_populates="rule", cascade="all, delete-orphan") def __repr__(self): return f"" class AlertHistory(Base): """告警历史表""" __tablename__ = "alert_history" id = Column(Integer, primary_key=True, autoincrement=True) rule_id = Column(Integer, ForeignKey("alert_rule.id", ondelete="CASCADE"), nullable=False, index=True) user_id = Column(Integer, nullable=False, index=True) symbol = Column(String(20), nullable=False, index=True) trigger_value = Column(Numeric(20, 4), nullable=True, comment="触发时的值") trigger_condition = Column(Text, nullable=True, comment="触发条件描述") notified = Column(Boolean, default=False, comment="是否已发送通知") notify_channels = Column(JSONB, nullable=True, comment="已发送的通知渠道") notify_time = Column(TIMESTAMP(timezone=True), nullable=True, comment="通知发送时间") trigger_time = Column(TIMESTAMP(timezone=True), server_default=func.now(), index=True, comment="触发时间") created_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) # 关系 rule = relationship("AlertRule", back_populates="history") def __repr__(self): return f"" # ============== Pydantic Schema ============== class AlertConditionSchema(BaseModel): """告警条件 Schema""" field: str = Field(..., description="字段名: price/change_percent/volume") operator: AlertOperator = Field(..., description="操作符: gt/lt/eq/ge/le/ne") value: float = Field(..., description="阈值") class Config: use_enum_values = True class AlertRuleCreate(BaseModel): """创建告警规则""" name: str = Field(..., min_length=1, max_length=100, description="告警名称") symbol: str = Field(..., min_length=1, max_length=20, description="品种代码") type: AlertType = Field(..., description="告警类型") condition: AlertConditionSchema = Field(..., description="触发条件") channels: List[NotifyChannel] = Field(..., min_items=1, description="通知渠道") enabled: bool = Field(default=True, description="是否启用") start_time: Optional[dt_time] = Field(None, description="生效开始时间") end_time: Optional[dt_time] = Field(None, description="生效结束时间") repeat_interval: int = Field(default=0, ge=0, description="重复间隔(秒)") class Config: use_enum_values = True class AlertRuleUpdate(BaseModel): """更新告警规则""" name: Optional[str] = Field(None, min_length=1, max_length=100) symbol: Optional[str] = Field(None, min_length=1, max_length=20) type: Optional[AlertType] = None condition: Optional[AlertConditionSchema] = None channels: Optional[List[NotifyChannel]] = None enabled: Optional[bool] = None start_time: Optional[dt_time] = None end_time: Optional[dt_time] = None repeat_interval: Optional[int] = Field(None, ge=0) class Config: use_enum_values = True class AlertRuleResponse(BaseModel): """告警规则响应""" id: int user_id: int name: str symbol: str type: str condition: dict channels: list enabled: bool start_time: Optional[str] end_time: Optional[str] repeat_interval: int last_triggered_at: Optional[datetime] trigger_count: int created_at: datetime updated_at: datetime class Config: from_attributes = True class AlertHistoryResponse(BaseModel): """告警历史响应""" id: int rule_id: int user_id: int symbol: str trigger_value: Optional[float] trigger_condition: Optional[str] notified: bool notify_channels: Optional[list] notify_time: Optional[datetime] trigger_time: datetime created_at: datetime class Config: from_attributes = True class AlertListResponse(BaseModel): """告警列表响应""" total: int page: int page_size: int items: List[AlertRuleResponse] class AlertHistoryListResponse(BaseModel): """告警历史列表响应""" total: int page: int page_size: int items: List[AlertHistoryResponse]