You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
6.5 KiB

# 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"<AlertRule(id={self.id}, name='{self.name}', symbol='{self.symbol}', type='{self.type}')>"
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"<AlertHistory(id={self.id}, rule_id={self.rule_id}, trigger_time='{self.trigger_time}')>"
# ============== 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]