Files
socks-manager/services/stats_service.py
T
Your Name 558bd8153f 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 项功能测试全部通过
2026-07-16 17:31:53 +08:00

198 lines
7.0 KiB
Python

"""监控与统计服务。"""
import logging
import os
import psutil
from collections import deque
from datetime import datetime, timezone, timedelta
from models import TrafficSnapshot, Connection, AuditLog, Instance, User
from database import db
log = logging.getLogger("socks.stats")
# 实时指标缓冲区(内存)
_metrics_buffer = deque(maxlen=600) # 10分钟 @ 10秒间隔
_metrics_lock = __import__("threading").Lock()
def _record_metrics():
"""记录系统级指标。"""
try:
cpu = psutil.cpu_percent(interval=0.5)
mem = psutil.virtual_memory()
net = psutil.net_io_counters()
# 计算活跃连接
with db.app.app_context():
active = db.session.query(Connection).filter_by(
state="active"
).count()
item = {
"ts": datetime.now(timezone.utc).isoformat(),
"cpu": cpu,
"mem_percent": mem.percent,
"mem_used_mb": mem.used // 1024 // 1024,
"mem_total_mb": mem.total // 1024 // 1024,
"net_bytes_sent": net.bytes_sent,
"net_bytes_recv": net.bytes_recv,
"active_connections": active,
}
with _metrics_lock:
_metrics_buffer.append(item)
except Exception as e:
log.error("记录指标失败: %s", e)
def get_realtime_metrics():
"""获取实时系统指标。"""
_record_metrics()
with _metrics_lock:
items = list(_metrics_buffer)
if not items:
_record_metrics()
items = list(_metrics_buffer)
return {
"latest": items[-1] if items else None,
"history": items[-60:], # 最近60个点(10分钟)
}
def get_traffic_trend(days=30):
"""获取流量趋势(每日快照)。"""
with db.app.app_context():
start = datetime.now(timezone.utc).date() - timedelta(days=days)
snapshots = db.session.query(
TrafficSnapshot.date,
db.func.sum(TrafficSnapshot.bytes_in).label("total_in"),
db.func.sum(TrafficSnapshot.bytes_out).label("total_out"),
db.func.sum(TrafficSnapshot.connections).label("total_conn"),
).filter(
TrafficSnapshot.date >= start
).group_by(TrafficSnapshot.date).order_by(
TrafficSnapshot.date
).all()
return [{
"date": s.date.isoformat(),
"bytes_in_mb": round((s.total_in or 0) / 1024 / 1024, 1),
"bytes_out_mb": round((s.total_out or 0) / 1024 / 1024, 1),
"connections": s.total_conn or 0,
} for s in snapshots]
def get_user_stats():
"""获取各用户流量排名。"""
from sqlalchemy import func
with db.app.app_context():
users = db.session.query(
User.username,
User.bytes_in,
User.bytes_out,
User.max_concurrent,
User.total_traffic_mb,
(User.bytes_in + User.bytes_out).label("total_bytes"),
).filter_by(enabled=True).order_by(
(User.bytes_in + User.bytes_out).desc()
).limit(20).all()
return [{
"username": u.username,
"bytes_in_mb": round(u.bytes_in / 1024 / 1024, 1),
"bytes_out_mb": round(u.bytes_out / 1024 / 1024, 1),
"total_mb": round(u.total_bytes / 1024 / 1024, 1),
"limit_mb": u.total_traffic_mb,
"usage_pct": round(
u.total_bytes / 1024 / 1024 / u.total_traffic_mb * 100, 1
) if u.total_traffic_mb else None,
} for u in users]
def get_instance_stats():
"""获取各实例流量统计。"""
with db.app.app_context():
instances = Instance.query.all()
result = []
for inst in instances:
conns = db.session.query(
db.func.sum(Connection.bytes_in).label("total_in"),
db.func.sum(Connection.bytes_out).label("total_out"),
db.func.count(Connection.id).label("total_conn"),
).filter(
Connection.instance_id == inst.id
).first()
result.append({
"id": inst.id,
"name": inst.name,
"host": inst.listen_host,
"port": inst.listen_port,
"bytes_in_mb": round((conns.total_in or 0) / 1024 / 1024, 1),
"bytes_out_mb": round((conns.total_out or 0) / 1024 / 1024, 1),
"total_connections": conns.total_conn or 0,
})
return result
def get_active_connections():
"""获取当前活跃连接列表。"""
with db.app.app_context():
conns = db.session.query(Connection).filter_by(
state="active"
).order_by(Connection.started_at.desc()).limit(50).all()
return [{
"id": c.id,
"instance": db.session.query(Instance).get(c.instance_id).name if c.instance_id else "",
"username": c.username,
"src": f"{c.src_ip}:{c.src_port}" if c.src_ip else "",
"dst": f"{c.dst_addr}:{c.dst_port}" if c.dst_addr else "",
"protocol": c.protocol,
"started": c.started_at.isoformat() if c.started_at else "",
"bytes_in": c.bytes_in,
"bytes_out": c.bytes_out,
} for c in conns]
def get_dashboard_summary():
"""仪表盘汇总。"""
metrics = get_realtime_metrics()
with db.app.app_context():
total_instances = db.session.query(Instance).count()
running_instances = db.session.query(Instance).filter_by(
enabled=True
).count()
total_users = db.session.query(User).count()
active_users = db.session.query(User).filter_by(
enabled=True, banned=False
).count()
active_conns = db.session.query(Connection).filter_by(
state="active"
).count()
today_logs = db.session.query(AuditLog).filter(
AuditLog.timestamp >= datetime.now(timezone.utc).replace(
hour=0, minute=0, second=0
)
).count()
return {
"system": metrics["latest"],
"total_instances": total_instances,
"running_instances": running_instances,
"total_users": total_users,
"active_users": active_users,
"active_connections": active_conns,
"today_logs": today_logs,
}
def prune_old_data():
"""清理旧数据(运行在后台)。"""
with db.app.app_context():
# 清理 30 天前的连接记录(closed的)
cutoff = datetime.now(timezone.utc) - timedelta(days=30)
db.session.query(Connection).filter(
Connection.state == "closed",
Connection.started_at < cutoff
).delete(synchronize_session=False)
# 清理 90 天前的审计日志
cutoff2 = datetime.now(timezone.utc) - timedelta(days=90)
db.session.query(AuditLog).filter(AuditLog.timestamp < cutoff2).delete(
synchronize_session=False
)
db.session.commit()
log.info("数据清理完成")