"""ORM 模型""" from datetime import datetime, timezone from sqlalchemy import ( Column, Integer, String, Boolean, Text, DateTime, ForeignKey, JSON, ) from sqlalchemy.orm import relationship from .database import Base from .crypto_storage import EncryptedText from .huya_defaults import ( HUYA_DEFAULT_BIND_ACT_ID, HUYA_DEFAULT_OUTER_ACT_ID, HUYA_DEFAULT_PAY_CHANNEL, HUYA_DEFAULT_ROOM_PID, HUYA_DEFAULT_SID, ) def _utcnow(): """返回时区感知的 UTC 当前时间,替代 _utcnow()。""" return datetime.now(timezone.utc) class User(Base): """系统用户""" __tablename__ = "users" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(64), unique=True, nullable=False, index=True) password_hash = Column(String(256), nullable=False) role = Column(String(32), nullable=False, default="support") # super_admin / operation / support is_active = Column(Boolean, default=True) remark = Column(String(256), default="") custom_permissions = Column(JSON, nullable=True, comment="自定义权限列表,null表示使用角色默认权限") created_at = Column(DateTime, default=_utcnow) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) # 客服被分配的账号 assigned_accounts = relationship("Account", back_populates="assigned_user", foreign_keys="Account.assigned_to") huya_accounts = relationship("HuyaAccount", back_populates="assigned_user", foreign_keys="HuyaAccount.assigned_to") class Account(Base): """斗鱼账号""" __tablename__ = "accounts" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(128), nullable=False) password = Column(EncryptedText(), nullable=False) email = Column(EncryptedText(), nullable=False) email_password = Column(EncryptedText(), nullable=False) email_imap_server = Column(String(128), default="") email_imap_port = Column(Integer, default=993) email_imap_ssl = Column(Boolean, default=True) assigned_to = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) tag = Column(String(64), default="") remark = Column(String(256), default="") uid = Column(String(32), default="", index=True) nickname = Column(String(128), default="") points = Column(Integer, nullable=True) game_name = Column(String(128), default="") game_channel = Column(String(128), default="") gold_balance = Column(Integer, nullable=True) exchange_balance = Column(Integer, nullable=True) bind_status = Column(String(32), default="") change_role_wait_time = Column(Integer, nullable=True) esports_points = Column(Integer, nullable=True) esports_game_name = Column(String(128), default="") esports_game_channel = Column(String(128), default="") esports_bind_status = Column(String(32), default="") esports_change_role_wait_time = Column(Integer, nullable=True) esports_can_change_time = Column(Integer, nullable=True) xpd_game_name = Column(String(128), default="") xpd_openid = Column(String(128), default="") xpd_role_id = Column(String(64), default="") xpd_plat_id = Column(Integer, nullable=True) xpd_area_id = Column(Integer, nullable=True) xpd_balance = Column(Integer, nullable=True) xpd_fragments = Column(Integer, nullable=True) xpd_bind_status = Column(String(32), default="") created_at = Column(DateTime, default=_utcnow) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) assigned_user = relationship("User", back_populates="assigned_accounts", foreign_keys=[assigned_to]) login_tasks = relationship("LoginTask", back_populates="account") douyu_tasks = relationship("DouyuTask", back_populates="account") class LoginTask(Base): """单次登录任务""" __tablename__ = "login_tasks" id = Column(Integer, primary_key=True, autoincrement=True) batch_id = Column(String(64), nullable=False, index=True) # 批次ID account_id = Column(Integer, ForeignKey("accounts.id"), nullable=False) status = Column(String(32), default="pending") # pending / running / success / failed / error cookie = Column(EncryptedText(), default="") message = Column(String(512), default="") created_by = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime, default=_utcnow) finished_at = Column(DateTime, nullable=True) # Cookie 有效性检测结果(持久化,刷新不丢失) ck_check_status = Column(String(16), default="") # valid / invalid / 空=未检测 ck_check_result = Column(JSON, nullable=True) # {fish_ball, nickname, level, message} ck_checked_at = Column(DateTime, nullable=True) account = relationship("Account", back_populates="login_tasks") class DouyuTask(Base): """斗鱼业务任务""" __tablename__ = "douyu_tasks" id = Column(Integer, primary_key=True, autoincrement=True) batch_id = Column(String(64), nullable=False, index=True) account_id = Column(Integer, ForeignKey("accounts.id"), nullable=False) task_type = Column(String(64), nullable=False, index=True) status = Column(String(32), default="pending") message = Column(String(512), default="") result = Column(JSON, nullable=True) created_by = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime, default=_utcnow) finished_at = Column(DateTime, nullable=True) account = relationship("Account", back_populates="douyu_tasks") class DouyuConfig(Base): """斗鱼业务配置""" __tablename__ = "douyu_config" id = Column(Integer, primary_key=True, autoincrement=True) manual_id = Column(String(64), default="G4KA4Qnz4LDp7") rid = Column(String(64), default="9263298") bind_act_alias = Column(String(64), default="20260120QYOOB") confirm_act_alias = Column(String(64), default="20260120QYOOB") legacy_act_alias = Column(String(64), default="cjm") room_id = Column(String(64), default="9263298") elite_amount = Column(Integer, default=3000) esports_manual_id = Column(String(64), default="1W0yGkOdDbAXb") esports_act_alias = Column(String(64), default="20260226ZMGDG") esports_amount = Column(Integer, default=3000) esports_chicken_gift_id = Column(String(64), default="24766") esports_chicken_skin_id = Column(String(64), default="0") esports_firework_gift_id = Column(String(64), default="24767") esports_firework_skin_id = Column(String(64), default="3850") xpd_act_alias = Column(String(64), default="20260623KDQFH") xpd_act_id = Column(String(64), default="46195") xpd_rid = Column(String(64), default="9263298") gold_pay_type = Column(Integer, default=1) gift_id = Column(String(64), default="23643") skin_id = Column(String(64), default="2942") updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class DouyuGoodsSnapshot(Base): """斗鱼兑换商品快照""" __tablename__ = "douyu_goods_snapshot" id = Column(Integer, primary_key=True, autoincrement=True) commodity_id = Column(String(64), nullable=False, index=True) name = Column(String(256), default="") score = Column(Integer, nullable=True) status = Column(String(32), default="") raw = Column(JSON, nullable=True) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class DouyuXpdGoodsSnapshot(Base): """斗鱼和平小店商品快照""" __tablename__ = "douyu_xpd_goods_snapshot" id = Column(Integer, primary_key=True, autoincrement=True) commodity_id = Column(String(64), nullable=False, index=True) name = Column(String(256), default="") price = Column(Integer, nullable=True) org_price = Column(Integer, nullable=True) category = Column(String(32), default="") goods_left = Column(Integer, nullable=True) raw = Column(JSON, nullable=True) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class DouyuEsportsGoodsSnapshot(Base): """斗鱼电竞手册皮肤商城商品快照""" __tablename__ = "douyu_esports_goods_snapshot" id = Column(Integer, primary_key=True, autoincrement=True) commodity_id = Column(String(64), nullable=False, index=True) name = Column(String(256), default="") score = Column(Integer, nullable=True) status = Column(String(32), default="") raw = Column(JSON, nullable=True) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class HuyaAccount(Base): """虎牙账号""" __tablename__ = "huya_accounts" id = Column(Integer, primary_key=True, autoincrement=True) uid = Column(String(32), default="", index=True) yyuid = Column(String(32), default="", index=True) username = Column(String(128), default="") account_password = Column(EncryptedText(), default="") nickname = Column(String(128), default="") cookie = Column(EncryptedText(), nullable=False) tag = Column(String(64), default="") remark = Column(String(256), default="") status = Column(String(32), default="imported") points = Column(Integer, nullable=True) game_name = Column(String(128), default="") game_channel = Column(String(64), default="") game_phone = Column(String(64), default="") sms_url = Column(Text, default="") assigned_to = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) created_at = Column(DateTime, default=_utcnow) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) assigned_user = relationship("User", back_populates="huya_accounts", foreign_keys=[assigned_to]) tasks = relationship("HuyaTask", back_populates="account") class HuyaRegisterBatch(Base): """虎牙自动注册批次(持久化元数据)""" __tablename__ = "huya_register_batches" id = Column(Integer, primary_key=True, autoincrement=True) batch_id = Column(String(64), unique=True, nullable=False, index=True) tag = Column(String(64), default="") created_by = Column(Integer, ForeignKey("users.id"), nullable=False, index=True) concurrency = Column(Integer, default=1) wait_seconds = Column(Integer, default=180) poll_interval = Column(Integer, default=5) password_prefix = Column(String(16), default="hy") fixed_password = Column(EncryptedText(), default="") use_proxy = Column(Boolean, default=False) status = Column(String(32), default="pending", index=True) message = Column(String(512), default="") total = Column(Integer, default=0) success_count = Column(Integer, default=0) failed_count = Column(Integer, default=0) stopped_count = Column(Integer, default=0) created_at = Column(DateTime, default=_utcnow) started_at = Column(DateTime, nullable=True) finished_at = Column(DateTime, nullable=True) items = relationship("HuyaRegisterItem", back_populates="batch", cascade="all, delete-orphan") class HuyaRegisterItem(Base): """虎牙自动注册批次内单条手机号状态""" __tablename__ = "huya_register_items" id = Column(Integer, primary_key=True, autoincrement=True) batch_db_id = Column(Integer, ForeignKey("huya_register_batches.id"), nullable=False, index=True) batch_id = Column(String(64), nullable=False, index=True) line = Column(Integer, nullable=False) phone = Column(String(64), default="", index=True) provider = Column(String(32), default="") sms_url = Column(Text, default="") status = Column(String(32), default="pending", index=True) message = Column(String(512), default="") code = Column(String(32), default="") change_code = Column(String(32), default="") attempts = Column(Integer, default=0) change_attempts = Column(Integer, default=0) account_id = Column(Integer, ForeignKey("huya_accounts.id"), nullable=True) username = Column(String(128), default="") uid = Column(String(32), default="") password = Column(EncryptedText(), default="") password_changed = Column(Boolean, default=False) cookie = Column(EncryptedText(), default="") started_at = Column(DateTime, nullable=True) finished_at = Column(DateTime, nullable=True) batch = relationship("HuyaRegisterBatch", back_populates="items") class HuyaRegisterSuccessLog(Base): """虎牙自动注册成功流水(只追加,供随时导出)""" __tablename__ = "huya_register_success_logs" id = Column(Integer, primary_key=True, autoincrement=True) batch_id = Column(String(64), default="", index=True) item_id = Column(Integer, nullable=True) account_id = Column(Integer, ForeignKey("huya_accounts.id"), nullable=True) phone = Column(String(64), default="", index=True) username = Column(String(128), default="") uid = Column(String(32), default="") password = Column(EncryptedText(), default="") sms_url = Column(Text, default="") tag = Column(String(64), default="", index=True) provider = Column(String(32), default="") created_by = Column(Integer, ForeignKey("users.id"), nullable=True) created_at = Column(DateTime, default=_utcnow, index=True) class HuyaTask(Base): """虎牙业务任务""" __tablename__ = "huya_tasks" id = Column(Integer, primary_key=True, autoincrement=True) batch_id = Column(String(64), nullable=False, index=True) account_id = Column(Integer, ForeignKey("huya_accounts.id"), nullable=False) task_type = Column(String(64), nullable=False, index=True) status = Column(String(32), default="pending") message = Column(String(512), default="") result = Column(JSON, nullable=True) created_by = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime, default=_utcnow) finished_at = Column(DateTime, nullable=True) account = relationship("HuyaAccount", back_populates="tasks") class HuyaConfig(Base): """虎牙业务配置""" __tablename__ = "huya_config" id = Column(Integer, primary_key=True, autoincrement=True) room_pid = Column(String(64), default=HUYA_DEFAULT_ROOM_PID) sid = Column(String(32), default=HUYA_DEFAULT_SID) outer_act_id = Column(String(32), default=HUYA_DEFAULT_OUTER_ACT_ID) bind_act_id = Column(String(32), default=HUYA_DEFAULT_BIND_ACT_ID) pay_channel = Column(String(16), default=HUYA_DEFAULT_PAY_CHANNEL) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class HuyaGoodsSnapshot(Base): """虎牙兑换商品快照""" __tablename__ = "huya_goods_snapshot" id = Column(Integer, primary_key=True, autoincrement=True) product_id = Column(String(64), nullable=False, index=True) name = Column(String(256), default="") price = Column(Integer, nullable=True) remain_text = Column(String(64), default="") raw = Column(JSON, nullable=True) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class HuyaRechargeGoodsSnapshot(Base): """虎牙充值商品快照""" __tablename__ = "huya_recharge_goods_snapshot" id = Column(Integer, primary_key=True, autoincrement=True) spu_id = Column(String(64), nullable=False, index=True) sku_id = Column(String(64), default="", index=True) name = Column(String(256), default="") price = Column(Integer, nullable=True) stock = Column(Integer, nullable=True) buy_limit = Column(Integer, nullable=True) icon = Column(String(512), default="") description = Column(String(512), default="") task_id = Column(String(64), default="") task_name = Column(String(256), default="") raw = Column(JSON, nullable=True) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) class ProxyConfig(Base): """代理配置(全局单条记录)""" __tablename__ = "proxy_config" id = Column(Integer, primary_key=True, autoincrement=True) enabled = Column(Boolean, default=False) api_url = Column(EncryptedText(), default="") http = Column(EncryptedText(), default="") https = Column(EncryptedText(), default="") # 白名单 whitelist_enabled = Column(Boolean, default=False) whitelist_platform = Column(String(32), default="xiequ") whitelist_credentials = Column(JSON, nullable=True) # 旧字段保留(向后兼容双写) whitelist_uid = Column(EncryptedText(), default="") whitelist_ukey = Column(EncryptedText(), default="") class AuditLog(Base): """操作审计日志""" __tablename__ = "audit_logs" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, nullable=True) username = Column(String(64), default="") action = Column(String(128), nullable=False) target = Column(String(256), default="") detail = Column(Text, default="") created_at = Column(DateTime, default=_utcnow)