feat: 重构为 SOCKS5 代理管理平台 v2.0
架构: - engine/server.py: asyncio SOCKS5 服务器引擎(含隧道/认证/限速) - engine/instances.py: 多实例管理器(独立事件循环/热启动/热停止) - services/user_service.py: 用户认证/流量统计/IP白名单/防爆破 - services/stats_service.py: 实时监控/流量趋势/连接追踪 - services/backup_service.py: 数据库备份与恢复 - api/v1.py: 完整 RESTful API - web/routes.py: Web 管理面板(暗色 Bootstrap 5 主题) 功能覆盖: ✅ 多实例 SOCKS5 管理(创建/启动/停止/重启/热加载) ✅ 用户认证(无认证/账号密码/流量上限/限速/并发限制) ✅ IP 白名单/黑名单/防爆破 ✅ 实时仪表盘(CPU/内存/网络/活跃连接) ✅ 流量统计(30天趋势图/用户排名/实例统计) ✅ 活跃连接追踪 ✅ 审计日志(事件/用户/IP 筛选) ✅ 数据库备份与恢复 ✅ 管理员密码保护 ✅ 16 项功能测试全部通过
This commit is contained in:
@@ -1,58 +1,155 @@
|
||||
import time
|
||||
"""SQLAlchemy 数据模型。"""
|
||||
from datetime import datetime, timezone
|
||||
from extensions import db
|
||||
from database import db
|
||||
|
||||
|
||||
class Proxy(db.Model):
|
||||
__tablename__ = "proxies"
|
||||
# ── SOCKS5 实例 ──────────────────────────────────────────────────
|
||||
class Instance(db.Model):
|
||||
__tablename__ = "instances"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128), nullable=False, index=True)
|
||||
host = db.Column(db.String(256), nullable=False)
|
||||
port = db.Column(db.Integer, nullable=False)
|
||||
username = db.Column(db.String(128), nullable=True)
|
||||
password = db.Column(db.String(256), nullable=True)
|
||||
group_id = db.Column(db.Integer, db.ForeignKey("groups.id"), nullable=True)
|
||||
name = db.Column(db.String(64), nullable=False, unique=True)
|
||||
engine = db.Column(db.String(16), default="builtin") # builtin / 3proxy
|
||||
listen_host = db.Column(db.String(64), default="0.0.0.0")
|
||||
listen_port = db.Column(db.Integer, nullable=False)
|
||||
timeout = db.Column(db.Integer, default=30)
|
||||
enabled = db.Column(db.Boolean, default=True)
|
||||
notes = db.Column(db.Text, nullable=True)
|
||||
notes = db.Column(db.Text)
|
||||
|
||||
# 健康状态(由检测任务更新)
|
||||
status = db.Column(db.String(16), default="unknown") # online/offline/unknown/error
|
||||
latency_ms = db.Column(db.Float, nullable=True)
|
||||
last_check = db.Column(db.DateTime, nullable=True)
|
||||
# 认证方式
|
||||
auth_method = db.Column(db.String(16), default="none") # none / userpass
|
||||
|
||||
# 流量限速 (Mbps)
|
||||
bandwidth_down = db.Column(db.Float, default=0) # 0=不限
|
||||
bandwidth_up = db.Column(db.Float, default=0)
|
||||
|
||||
# 并发限制
|
||||
max_concurrent = db.Column(db.Integer, default=10)
|
||||
|
||||
# 创建时间
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
||||
# 运行状态 (运行时更新)
|
||||
running = db.Column(db.Boolean, default=False)
|
||||
active_connections = db.Column(db.Integer, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Instance {self.name} {self.listen_host}:{self.listen_port}>"
|
||||
|
||||
|
||||
# ── 代理用户 ─────────────────────────────────────────────────────
|
||||
class User(db.Model):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), nullable=False, unique=True, index=True)
|
||||
password = db.Column(db.String(128), nullable=True) # 明文或哈希;认证方式为 userpass 时必须
|
||||
enabled = db.Column(db.Boolean, default=True)
|
||||
|
||||
# 流量限制
|
||||
total_traffic_mb = db.Column(db.Float, default=0) # 0=不限
|
||||
monthly_traffic_mb = db.Column(db.Float, default=0) # 0=不限
|
||||
|
||||
# 限速 (Mbps)
|
||||
bandwidth_down = db.Column(db.Float, default=0)
|
||||
bandwidth_up = db.Column(db.Float, default=0)
|
||||
|
||||
# 并发连接
|
||||
max_concurrent = db.Column(db.Integer, default=10)
|
||||
|
||||
# IP 白名单/黑名单(逗号分隔)
|
||||
ip_whitelist = db.Column(db.Text, default="")
|
||||
ip_blacklist = db.Column(db.Text, default="")
|
||||
|
||||
# 生命周期
|
||||
expire_at = db.Column(db.DateTime, nullable=True) # None=不过期
|
||||
banned = db.Column(db.Boolean, default=False)
|
||||
|
||||
# 统计
|
||||
bytes_in = db.Column(db.BigInteger, default=0)
|
||||
bytes_out = db.Column(db.BigInteger, default=0)
|
||||
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
||||
group = db.relationship("Group", backref=db.backref("proxies", lazy=True))
|
||||
def is_active(self):
|
||||
"""用户是否可用(未过期、未封禁、已启用)"""
|
||||
if not self.enabled or self.banned:
|
||||
return False
|
||||
if self.expire_at and datetime.now(timezone.utc) > self.expire_at:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Proxy {self.name} {self.host}:{self.port}>"
|
||||
return f"<User {self.username}>"
|
||||
|
||||
|
||||
class Group(db.Model):
|
||||
__tablename__ = "groups"
|
||||
# ── 实时连接 ─────────────────────────────────────────────────────
|
||||
class Connection(db.Model):
|
||||
__tablename__ = "connections"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128), nullable=False, unique=True)
|
||||
color = db.Column(db.String(16), default="#6366f1")
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
id = db.Column(db.String(64), primary_key=True)
|
||||
instance_id = db.Column(db.Integer, db.ForeignKey("instances.id"), nullable=True)
|
||||
username = db.Column(db.String(64), nullable=True)
|
||||
src_ip = db.Column(db.String(64), nullable=True)
|
||||
src_port = db.Column(db.Integer, nullable=True)
|
||||
dst_addr = db.Column(db.String(256), nullable=True)
|
||||
dst_port = db.Column(db.Integer, nullable=True)
|
||||
protocol = db.Column(db.String(8), default="SOCKS5")
|
||||
state = db.Column(db.String(16), default="active") # active / closed / rejected
|
||||
started_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
ended_at = db.Column(db.DateTime, nullable=True)
|
||||
bytes_in = db.Column(db.BigInteger, default=0)
|
||||
bytes_out = db.Column(db.BigInteger, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Group {self.name}>"
|
||||
return f"<Connection {self.id[:12]} {self.src_ip} -> {self.dst_addr}:{self.dst_port}>"
|
||||
|
||||
|
||||
class Log(db.Model):
|
||||
__tablename__ = "logs"
|
||||
# ── 审计日志 ─────────────────────────────────────────────────────
|
||||
class AuditLog(db.Model):
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
timestamp = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
level = db.Column(db.String(16), default="INFO") # INFO/WARN/ERROR
|
||||
action = db.Column(db.String(64), nullable=True) # add/update/delete/check
|
||||
target = db.Column(db.String(128), nullable=True) # proxy/group name
|
||||
detail = db.Column(db.Text, nullable=True)
|
||||
event = db.Column(db.String(32), nullable=False, index=True)
|
||||
# event: auth_success, auth_fail, connect_success, connect_reject, user_create,
|
||||
# user_update, instance_start, instance_stop, admin_action
|
||||
user = db.Column(db.String(64), nullable=True)
|
||||
instance_id = db.Column(db.Integer, nullable=True)
|
||||
src_ip = db.Column(db.String(64), nullable=True)
|
||||
detail = db.Column(db.Text)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Log [{self.level}] {self.action}>"
|
||||
return f"<AuditLog {self.event} {self.user}>"
|
||||
|
||||
|
||||
# ── 备份记录 ─────────────────────────────────────────────────────
|
||||
class Backup(db.Model):
|
||||
__tablename__ = "backups"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
filename = db.Column(db.String(256), nullable=False)
|
||||
path = db.Column(db.String(512), nullable=False)
|
||||
size = db.Column(db.BigInteger, default=0)
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
note = db.Column(db.Text)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Backup {self.filename}>"
|
||||
|
||||
|
||||
# ── 流量快照(每日) ────────────────────────────────────────────
|
||||
class TrafficSnapshot(db.Model):
|
||||
__tablename__ = "traffic_snapshots"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
date = db.Column(db.Date, nullable=False, unique=True)
|
||||
instance_id = db.Column(db.Integer, db.ForeignKey("instances.id"), nullable=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
|
||||
bytes_in = db.Column(db.BigInteger, default=0)
|
||||
bytes_out = db.Column(db.BigInteger, default=0)
|
||||
connections = db.Column(db.Integer, default=0)
|
||||
|
||||
Reference in New Issue
Block a user