mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-05-07 05:12:42 +08:00
184 lines
7.4 KiB
Python
184 lines
7.4 KiB
Python
"""
|
|
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', 'custom_domain'
|
|
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) # 订阅开通时间
|
|
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', 'custom_domain'
|
|
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 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)
|
|
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,
|
|
'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}" |