feat:自动更换ip+流量监控
This commit is contained in:
180
database.py
Normal file
180
database.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""数据库模型和配置管理"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text, Float, create_engine
|
||||
from sqlalchemy.orm import Session, declarative_base, relationship, sessionmaker
|
||||
|
||||
# 加载 .env 配置文件
|
||||
APP_DIR = Path(__file__).resolve().parent
|
||||
load_dotenv(APP_DIR / ".env")
|
||||
|
||||
# 路径配置
|
||||
DATA_DIR = APP_DIR / "data"
|
||||
LOG_DIR = APP_DIR / "logs"
|
||||
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 数据库配置
|
||||
# MySQL 连接格式: mysql+pymysql://用户名:密码@主机:端口/数据库名?charset=utf8mb4
|
||||
# 在 .env 文件中配置 DATABASE_URL
|
||||
DEFAULT_MYSQL_URL = "mysql+pymysql://proxyauto:proxyauto@localhost:3306/proxyauto?charset=utf8mb4"
|
||||
DATABASE_URL = os.environ.get("DATABASE_URL", DEFAULT_MYSQL_URL)
|
||||
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
echo=False,
|
||||
pool_pre_ping=True, # 自动重连
|
||||
pool_recycle=3600, # 连接池回收时间
|
||||
)
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""简单的密码哈希"""
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""用户表"""
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
username = Column(String(64), nullable=False, unique=True)
|
||||
password_hash = Column(String(128), nullable=False)
|
||||
is_admin = Column(Boolean, nullable=False, default=False)
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow)
|
||||
updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow)
|
||||
|
||||
def check_password(self, password: str) -> bool:
|
||||
return self.password_hash == hash_password(password)
|
||||
|
||||
|
||||
class ProxyMachine(Base):
|
||||
"""代理机器表 - 每台机器可独立配置域名和更换时间"""
|
||||
__tablename__ = "proxy_machines"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(128), nullable=False, unique=True)
|
||||
aws_service = Column(String(32), nullable=False, default="ec2") # ec2 | lightsail
|
||||
aws_region = Column(String(64), nullable=False, default="us-east-1")
|
||||
aws_instance_id = Column(String(64), nullable=False)
|
||||
note = Column(String(255), nullable=True)
|
||||
enabled = Column(Boolean, nullable=False, default=True)
|
||||
|
||||
# 域名配置 - 每台机器独立绑定
|
||||
cf_zone_id = Column(String(64), nullable=True)
|
||||
cf_record_name = Column(String(255), nullable=True)
|
||||
cf_record_id = Column(String(64), nullable=True)
|
||||
cf_proxied = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
# 独立的更换时间配置
|
||||
change_interval_seconds = Column(Integer, nullable=False, default=3600)
|
||||
auto_enabled = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
# 当前状态
|
||||
current_ip = Column(String(64), nullable=True)
|
||||
last_run_at = Column(DateTime(timezone=True), nullable=True)
|
||||
last_success = Column(Boolean, nullable=True)
|
||||
last_message = Column(Text, nullable=True)
|
||||
|
||||
# 流量预警配置
|
||||
# Lightsail: 总流量预警(上传+下载),单位 GB
|
||||
# EC2: 上传流量预警,单位 GB
|
||||
traffic_alert_enabled = Column(Boolean, nullable=False, default=False)
|
||||
traffic_alert_limit_gb = Column(Float, nullable=True) # 流量限制(GB)
|
||||
traffic_alert_triggered = Column(Boolean, nullable=False, default=False) # 是否已触发预警
|
||||
traffic_last_check_at = Column(DateTime(timezone=True), nullable=True) # 上次检查时间
|
||||
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow)
|
||||
updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow)
|
||||
|
||||
|
||||
class Config(Base):
|
||||
"""全局配置表 - AWS/Cloudflare 凭证"""
|
||||
__tablename__ = "configs"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
# AWS 凭证(全局共用)
|
||||
aws_access_key = Column(String(255), nullable=True)
|
||||
aws_secret_key = Column(String(255), nullable=True)
|
||||
|
||||
# Cloudflare 凭证(全局共用)
|
||||
cloudflare_auth_type = Column(String(32), nullable=False, default="api_token") # api_token | global_key
|
||||
cf_api_token = Column(String(255), nullable=True)
|
||||
cf_email = Column(String(255), nullable=True)
|
||||
cf_api_key = Column(String(255), nullable=True)
|
||||
|
||||
# 全局设置
|
||||
release_old_eip = Column(Boolean, nullable=False, default=True)
|
||||
|
||||
# 邮件通知配置
|
||||
smtp_host = Column(String(255), nullable=True)
|
||||
smtp_port = Column(Integer, nullable=True, default=587)
|
||||
smtp_user = Column(String(255), nullable=True)
|
||||
smtp_password = Column(String(255), nullable=True)
|
||||
smtp_use_tls = Column(Boolean, nullable=False, default=True)
|
||||
alert_email = Column(String(255), nullable=True) # 接收预警的邮箱
|
||||
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow)
|
||||
updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow)
|
||||
|
||||
|
||||
# 创建表
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
|
||||
def get_session() -> Session:
|
||||
return SessionLocal()
|
||||
|
||||
|
||||
def ensure_singleton_config(session: Session) -> Config:
|
||||
config = session.query(Config).order_by(Config.id.asc()).first()
|
||||
if config:
|
||||
return config
|
||||
|
||||
config = Config(
|
||||
cloudflare_auth_type="api_token",
|
||||
release_old_eip=True,
|
||||
)
|
||||
session.add(config)
|
||||
session.commit()
|
||||
return config
|
||||
|
||||
|
||||
def ensure_admin_user(session: Session) -> User:
|
||||
"""确保管理员用户存在"""
|
||||
admin = session.query(User).filter_by(username="admin").first()
|
||||
if admin:
|
||||
return admin
|
||||
|
||||
admin = User(
|
||||
username="admin",
|
||||
password_hash=hash_password("80012029Lz@"),
|
||||
is_admin=True,
|
||||
)
|
||||
session.add(admin)
|
||||
session.commit()
|
||||
return admin
|
||||
|
||||
|
||||
def mask_secret(value: Optional[str], keep_start: int = 3, keep_end: int = 3) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
text = value.strip()
|
||||
if len(text) <= keep_start + keep_end + 2:
|
||||
return "••••••"
|
||||
return f"{text[:keep_start]}••••••{text[-keep_end:]}"
|
||||
Reference in New Issue
Block a user