from sqlalchemy import create_engine, Column, Integer, String, JSON, Boolean, Table, ForeignKey, DateTime, Text from sqlalchemy.orm import declarative_base, sessionmaker, relationship, backref import os from datetime import datetime Base = declarative_base() class Target(Base): __tablename__ = 'targets' id = Column(Integer, primary_key=True) name = Column(String, unique=True, index=True) url = Column(String) timeout_ms = Column(Integer, default=5000) class NotificationChannel(Base): __tablename__ = 'notification_channels' id = Column(Integer, primary_key=True) name = Column(String, unique=True) channel_type = Column(String) # feishu, wecom webhook_url = Column(String) class MessageTemplate(Base): __tablename__ = 'message_templates' id = Column(Integer, primary_key=True) name = Column(String, unique=True) # 方便识别,如 "收款成功通知" template_content = Column(Text) # "收到{amt}元" class WebhookEndpoint(Base): __tablename__ = 'webhook_endpoints' id = Column(Integer, primary_key=True) namespace = Column(String, unique=True, index=True) description = Column(String, nullable=True) is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=datetime.utcnow) rules = relationship("ProcessingRule", back_populates="endpoint", cascade="all, delete-orphan") class ProcessingRule(Base): __tablename__ = 'processing_rules' id = Column(Integer, primary_key=True) endpoint_id = Column(Integer, ForeignKey('webhook_endpoints.id')) endpoint = relationship("WebhookEndpoint", back_populates="rules") # Tree structure support parent_rule_id = Column(Integer, ForeignKey('processing_rules.id'), nullable=True) children = relationship("ProcessingRule", backref=backref('parent', remote_side=[id]), cascade="all, delete-orphan") priority = Column(Integer, default=0) # Higher executes first (if we want ordering) match_field = Column(String) # e.g. "trans_order_info.remark" or "event_define_no" operator = Column(String, default="eq") # eq, neq, contains, startswith, regex match_value = Column(String) # e.g. "imcgcd03" or "pay.success" actions = relationship("RuleAction", back_populates="rule", cascade="all, delete-orphan") class RuleAction(Base): __tablename__ = 'rule_actions' id = Column(Integer, primary_key=True) rule_id = Column(Integer, ForeignKey('processing_rules.id')) rule = relationship("ProcessingRule", back_populates="actions") action_type = Column(String) # "forward" or "notify" # Forward params target_id = Column(Integer, ForeignKey('targets.id'), nullable=True) target = relationship("Target") # Notify params channel_id = Column(Integer, ForeignKey('notification_channels.id'), nullable=True) channel = relationship("NotificationChannel") template_id = Column(Integer, ForeignKey('message_templates.id'), nullable=True) template = relationship("MessageTemplate") # Extra params for templating (e.g. {"pay_method": "微信"}) template_vars = Column(JSON, nullable=True) class RequestLog(Base): __tablename__ = 'request_logs' id = Column(Integer, primary_key=True) namespace = Column(String, index=True) remark = Column(String, nullable=True) # 保留用于快速筛选,可选 event_no = Column(String, nullable=True) # 保留用于快速筛选,可选 raw_body = Column(JSON) received_at = Column(DateTime, default=datetime.utcnow) status = Column(String) # success, error delivery_logs = relationship("DeliveryLog", back_populates="request_log", cascade="all, delete-orphan") class DeliveryLog(Base): __tablename__ = 'delivery_logs' id = Column(Integer, primary_key=True) request_id = Column(Integer, ForeignKey('request_logs.id')) target_name = Column(String) type = Column(String) # relay, notify status = Column(String) # success, failed response_summary = Column(Text, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) request_log = relationship("RequestLog", back_populates="delivery_logs") DB_PATH = os.getenv("DB_PATH", "sqlite:///./config/data.db") engine = create_engine(DB_PATH, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) def init_db(): Base.metadata.create_all(bind=engine)