Initial commit

This commit is contained in:
doujiang
2026-03-21 23:00:03 +08:00
commit 8f51c3d378
92 changed files with 28858 additions and 0 deletions

229
src/database/models.py Normal file
View File

@@ -0,0 +1,229 @@
"""
SQLAlchemy ORM 模型定义
"""
from datetime import datetime
from typing import Optional, Dict, Any
import json
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.types import TypeDecorator
from sqlalchemy.orm import relationship
Base = declarative_base()
class JSONEncodedDict(TypeDecorator):
"""JSON 编码字典类型"""
impl = Text
def process_bind_param(self, value: Optional[Dict[str, Any]], dialect):
if value is None:
return None
return json.dumps(value, ensure_ascii=False)
def process_result_value(self, value: Optional[str], dialect):
if value is None:
return None
return json.loads(value)
class Account(Base):
"""已注册账号表"""
__tablename__ = 'accounts'
id = Column(Integer, primary_key=True, autoincrement=True)
email = Column(String(255), nullable=False, unique=True, index=True)
password = Column(String(255)) # 注册密码(明文存储)
access_token = Column(Text)
refresh_token = Column(Text)
id_token = Column(Text)
session_token = Column(Text) # 会话令牌(优先刷新方式)
client_id = Column(String(255)) # OAuth Client ID
account_id = Column(String(255))
workspace_id = Column(String(255))
email_service = Column(String(50), nullable=False) # 'tempmail', 'outlook', 'moe_mail'
email_service_id = Column(String(255)) # 邮箱服务中的ID
proxy_used = Column(String(255))
registered_at = Column(DateTime, default=datetime.utcnow)
last_refresh = Column(DateTime) # 最后刷新时间
expires_at = Column(DateTime) # Token 过期时间
status = Column(String(20), default='active') # 'active', 'expired', 'banned', 'failed'
extra_data = Column(JSONEncodedDict) # 额外信息存储
cpa_uploaded = Column(Boolean, default=False) # 是否已上传到 CPA
cpa_uploaded_at = Column(DateTime) # 上传时间
source = Column(String(20), default='register') # 'register' 或 'login',区分账号来源
subscription_type = Column(String(20)) # None / 'plus' / 'team'
subscription_at = Column(DateTime) # 订阅开通时间
cookies = Column(Text) # 完整 cookie 字符串,用于支付请求
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
'id': self.id,
'email': self.email,
'password': self.password,
'client_id': self.client_id,
'email_service': self.email_service,
'account_id': self.account_id,
'workspace_id': self.workspace_id,
'registered_at': self.registered_at.isoformat() if self.registered_at else None,
'last_refresh': self.last_refresh.isoformat() if self.last_refresh else None,
'expires_at': self.expires_at.isoformat() if self.expires_at else None,
'status': self.status,
'proxy_used': self.proxy_used,
'cpa_uploaded': self.cpa_uploaded,
'cpa_uploaded_at': self.cpa_uploaded_at.isoformat() if self.cpa_uploaded_at else None,
'source': self.source,
'subscription_type': self.subscription_type,
'subscription_at': self.subscription_at.isoformat() if self.subscription_at else None,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
class EmailService(Base):
"""邮箱服务配置表"""
__tablename__ = 'email_services'
id = Column(Integer, primary_key=True, autoincrement=True)
service_type = Column(String(50), nullable=False) # 'outlook', 'moe_mail'
name = Column(String(100), nullable=False)
config = Column(JSONEncodedDict, nullable=False) # 服务配置(加密存储)
enabled = Column(Boolean, default=True)
priority = Column(Integer, default=0) # 使用优先级
last_used = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class RegistrationTask(Base):
"""注册任务表"""
__tablename__ = 'registration_tasks'
id = Column(Integer, primary_key=True, autoincrement=True)
task_uuid = Column(String(36), unique=True, nullable=False, index=True) # 任务唯一标识
status = Column(String(20), default='pending') # 'pending', 'running', 'completed', 'failed', 'cancelled'
email_service_id = Column(Integer, ForeignKey('email_services.id'), index=True) # 使用的邮箱服务
proxy = Column(String(255)) # 使用的代理
logs = Column(Text) # 注册过程日志
result = Column(JSONEncodedDict) # 注册结果
error_message = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
started_at = Column(DateTime)
completed_at = Column(DateTime)
# 关系
email_service = relationship('EmailService')
class Setting(Base):
"""系统设置表"""
__tablename__ = 'settings'
key = Column(String(100), primary_key=True)
value = Column(Text)
description = Column(Text)
category = Column(String(50), default='general') # 'general', 'email', 'proxy', 'openai'
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class CpaService(Base):
"""CPA 服务配置表"""
__tablename__ = 'cpa_services'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False) # 服务名称
api_url = Column(String(500), nullable=False) # API URL
api_token = Column(Text, nullable=False) # API Token
enabled = Column(Boolean, default=True)
priority = Column(Integer, default=0) # 优先级
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Sub2ApiService(Base):
"""Sub2API 服务配置表"""
__tablename__ = 'sub2api_services'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False) # 服务名称
api_url = Column(String(500), nullable=False) # API URL (host)
api_key = Column(Text, nullable=False) # x-api-key
enabled = Column(Boolean, default=True)
priority = Column(Integer, default=0) # 优先级
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class TeamManagerService(Base):
"""Team Manager 服务配置表"""
__tablename__ = 'tm_services'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False) # 服务名称
api_url = Column(String(500), nullable=False) # API URL
api_key = Column(Text, nullable=False) # X-API-Key
enabled = Column(Boolean, default=True)
priority = Column(Integer, default=0) # 优先级
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Proxy(Base):
"""代理列表表"""
__tablename__ = 'proxies'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False) # 代理名称
type = Column(String(20), nullable=False, default='http') # http, socks5
host = Column(String(255), nullable=False)
port = Column(Integer, nullable=False)
username = Column(String(100))
password = Column(String(255))
enabled = Column(Boolean, default=True)
is_default = Column(Boolean, default=False) # 是否为默认代理
priority = Column(Integer, default=0) # 优先级(保留字段)
last_used = Column(DateTime) # 最后使用时间
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self, include_password: bool = False) -> Dict[str, Any]:
"""转换为字典"""
result = {
'id': self.id,
'name': self.name,
'type': self.type,
'host': self.host,
'port': self.port,
'username': self.username,
'enabled': self.enabled,
'is_default': self.is_default or False,
'priority': self.priority,
'last_used': self.last_used.isoformat() if self.last_used else None,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}
if include_password:
result['password'] = self.password
else:
result['has_password'] = bool(self.password)
return result
@property
def proxy_url(self) -> str:
"""获取完整的代理 URL"""
if self.type == "http":
scheme = "http"
elif self.type == "socks5":
scheme = "socks5"
else:
scheme = self.type
auth = ""
if self.username and self.password:
auth = f"{self.username}:{self.password}@"
return f"{scheme}://{auth}{self.host}:{self.port}"