This commit is contained in:
Your Name
2026-07-23 17:56:25 +08:00
parent a05dae8412
commit 4970d8f8d3
4262 changed files with 735221 additions and 0 deletions
+278
View File
@@ -0,0 +1,278 @@
from datetime import datetime
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, UniqueConstraint
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, index=True, nullable=False)
email = Column(String(255), unique=True, index=True, nullable=True)
password_hash = Column(String(255), nullable=False)
display_name = Column(String(100), nullable=True)
role = Column(String(20), default="operator", index=True) # admin, operator, viewer
is_active = Column(Boolean, default=True)
email_verified = Column(Boolean, default=False)
email_verified_at = Column(DateTime, nullable=True)
max_accounts = Column(Integer, default=3)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
accounts = relationship("Account", back_populates="owner")
verification_tokens = relationship(
"EmailVerificationToken",
back_populates="user",
cascade="all, delete-orphan",
)
password_reset_tokens = relationship(
"PasswordResetToken",
back_populates="user",
cascade="all, delete-orphan",
)
class EmailVerificationToken(Base):
__tablename__ = "email_verification_tokens"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
token = Column(String(128), unique=True, index=True, nullable=False)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="verification_tokens")
class PasswordResetToken(Base):
__tablename__ = "password_reset_tokens"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
token = Column(String(128), unique=True, index=True, nullable=False)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="password_reset_tokens")
class AppConfig(Base):
"""系统功能配置(单例行 id=1,JSON 存储)。"""
__tablename__ = "app_config"
id = Column(Integer, primary_key=True)
data = Column(Text, nullable=False, default="{}")
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True, index=True)
owner_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
username = Column(String(100), unique=True, index=True, nullable=True) # 登录后获取的用户名/ID
avatar_url = Column(String(512), nullable=True) # 登录后获取的头像 URL
douyin_uid = Column(String(64), nullable=True, index=True) # 抖音 UID
phone = Column(String(20), nullable=True) # 绑定的手机号(可选)
status = Column(String(50), default="offline") # offline, logging_in, online, error
cookie_path = Column(String(255), nullable=True) # 存储 cookie/session 的路径
cookie_data = Column(Text, nullable=True) # Playwright storage_state JSON
cookie_updated_at = Column(DateTime, nullable=True) # Cookie 最近更新时间
im_session_data = Column(Text, nullable=True) # IM 直连会话 (WS URL / device_id 等)
reply_delay_seconds = Column(Integer, default=0) # 账号回复排队间隔;0/NULL=继承系统默认
reply_cooldown_seconds = Column(Integer, nullable=True) # 自动回复冷却秒数;NULL=继承全局设置
follow_welcome_enabled = Column(Boolean, default=False) # 新粉丝关注后自动发送欢迎语
follow_welcome_content = Column(Text, nullable=True) # 关注欢迎语内容(空=不发)
user_agent = Column(Text, nullable=True) # 伪装设备头(User-Agent),空=默认
qr_code_base64 = Column(Text, nullable=True) # 当前登录二维码的 base64 字符串
error_message = Column(Text, nullable=True) # 错误信息
quota_disabled = Column(Boolean, default=False, index=True) # 额度不足被停用
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联
owner = relationship("User", back_populates="accounts")
rules = relationship("AutoReplyRule", back_populates="account", cascade="all, delete-orphan")
logs = relationship("MessageLog", back_populates="account", cascade="all, delete-orphan")
profile_detail = relationship(
"AccountProfileDetail",
back_populates="account",
uselist=False,
cascade="all, delete-orphan",
)
videos = relationship(
"AccountVideo",
back_populates="account",
cascade="all, delete-orphan",
order_by="AccountVideo.sort_order",
)
class AccountProfileDetail(Base):
"""托管账号抖音详细资料(本地缓存)。"""
__tablename__ = "account_profile_details"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"), unique=True, nullable=False, index=True)
uid = Column(String(64), nullable=True)
nickname = Column(String(200), nullable=True)
avatar_url = Column(String(512), nullable=True)
unique_id = Column(String(100), nullable=True)
signature = Column(Text, nullable=True)
sec_user_id = Column(String(255), nullable=True)
video_count = Column(Integer, nullable=True)
follower_count = Column(Integer, nullable=True)
following_count = Column(Integer, nullable=True)
total_favorited = Column(Integer, nullable=True)
favoriting_count = Column(Integer, nullable=True)
sync_message = Column(Text, nullable=True)
synced_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
account = relationship("Account", back_populates="profile_detail")
class AccountVideo(Base):
"""托管账号已发布作品(本地缓存)。"""
__tablename__ = "account_videos"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False, index=True)
aweme_id = Column(String(64), nullable=False, index=True)
title = Column(Text, nullable=True)
cover_url = Column(String(1024), nullable=True)
video_url = Column(String(2048), nullable=True)
share_url = Column(String(1024), nullable=True)
create_time = Column(DateTime, nullable=True)
digg_count = Column(Integer, nullable=True)
comment_count = Column(Integer, nullable=True)
play_count = Column(Integer, nullable=True)
media_type = Column(String(20), nullable=True) # video, image, other
sort_order = Column(Integer, default=0, index=True)
synced_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
account = relationship("Account", back_populates="videos")
class LinkCardPage(Base):
"""规则回复用的链接卡片落地页(带 SEO meta,打开后跳转至目标 URL)。"""
__tablename__ = "link_card_pages"
id = Column(Integer, primary_key=True, index=True)
owner_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
slug = Column(String(64), unique=True, index=True, nullable=False)
title = Column(String(200), nullable=False)
content = Column(Text, nullable=True)
target_url = Column(String(2000), nullable=False)
image_path = Column(String(512), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class AutoReplyRule(Base):
__tablename__ = "rules"
id = Column(Integer, primary_key=True, index=True)
owner_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=True) # 为空代表全局规则
keyword = Column(String(255), index=True) # 触发关键词,或者空代表兜底
reply_content = Column(Text) # 回复内容
match_type = Column(String(50), default="contains") # exact (精确), contains (包含), regex (正则), default (兜底)
sort_order = Column(Integer, default=0, index=True) # 规则优先级,越小越优先
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
account = relationship("Account", back_populates="rules")
class MessageLog(Base):
__tablename__ = "message_logs"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"))
sender_name = Column(String(100)) # 发送者名字
sender_id = Column(String(100), nullable=True) # 发送者唯一ID
sender_avatar = Column(String(512), nullable=True) # 发送者头像 URL
message_content = Column(Text) # 接收到的消息
reply_content = Column(Text, nullable=True) # 回复的消息
status = Column(String(50), default="received") # received, replied, ignored, failed
error_message = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
account = relationship("Account", back_populates="logs")
class ReceivedMessageLog(Base):
"""接收消息原始日志:仅记录收到的消息,内容原样保存。"""
__tablename__ = "received_message_logs"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False, index=True)
conversation_id = Column(String(128), nullable=True, index=True)
sender_id = Column(String(100), nullable=True, index=True)
sender_name = Column(String(100), nullable=True)
sender_avatar = Column(String(512), nullable=True)
message_type = Column(Integer, nullable=True)
server_message_id = Column(String(64), nullable=True, index=True)
raw_content = Column(Text, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, index=True)
class FollowWelcomeLog(Base):
"""关注欢迎语去重表:每个账号对每个新粉丝只发送一次欢迎语(重启后仍生效)。"""
__tablename__ = "follow_welcome_logs"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False, index=True)
follower_uid = Column(String(64), nullable=False, index=True) # 新粉丝的抖音 UID
status = Column(String(20), default="sent") # sent, failed
detail = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
__table_args__ = (
UniqueConstraint("account_id", "follower_uid", name="uq_follow_welcome_account_follower"),
)
class SystemLog(Base):
"""系统诊断日志:记录私信收发/连接/鉴权等链路事件,便于排查失败原因。"""
__tablename__ = "system_logs"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, nullable=True, index=True) # 关联账号(可空,全局事件)
level = Column(String(20), default="info", index=True) # info, success, warning, error
category = Column(String(40), default="system", index=True) # ws, send, recv, auth, poll, system
event = Column(String(255)) # 事件标题
detail = Column(Text, nullable=True) # 详细原因
created_at = Column(DateTime, default=datetime.utcnow, index=True)
class PaymentOrder(Base):
"""账号额度购买订单。"""
__tablename__ = "payment_orders"
id = Column(Integer, primary_key=True, index=True)
order_no = Column(String(64), unique=True, index=True, nullable=False)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
channel = Column(String(20), nullable=False) # wechat, alipay
slots = Column(Integer, nullable=False)
amount_fen = Column(Integer, nullable=False)
status = Column(String(20), default="pending", index=True) # pending, paid, expired, cancelled, refunded
slots_applied = Column(Boolean, default=False)
qr_code = Column(Text, nullable=True)
pay_url = Column(Text, nullable=True)
trade_no = Column(String(128), nullable=True)
notify_payload = Column(Text, nullable=True)
paid_at = Column(DateTime, nullable=True)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)