深度优化: 修复跨服务器登录、密码哈希、仪表盘UI、流量趋势等12项问题
### 严重修复 1. 跨服务器无法登录 - 新增SESSION_COOKIE_DOMAIN/SECURE/NAME配置 2. SOCKS5用户密码明文存储→bcrypt哈希(兼容自动迁移) 3. 仪表盘审计日志不显示→替换为空循环为真实API加载 4. 管理员登录防爆破未生效→auth.py集成check_fail2ban ### 中等问题修复 5. 流量趋势图表无数据→新增record_traffic_snapshot后台线程 6. 网络流量累计值跳变→新增基线差值机制 7. asyncio连接计数非线程安全→加锁保护 ### 优化改进 8. API密钥页面截断文本修复 9. 新增gunicorn/bcrypt依赖 10. 更新README环境变量文档和生产建议 11. 登录密码常量时间对比防时序攻击 12. 仪表盘图表添加animation:false防卡顿
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""监控与统计服务。"""
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
@@ -14,13 +15,34 @@ log = logging.getLogger("socks.stats")
|
||||
_metrics_buffer = deque(maxlen=600) # 10分钟 @ 10秒间隔
|
||||
_metrics_lock = __import__("threading").Lock()
|
||||
|
||||
# 网络流量基线(用于计算会话增量)
|
||||
_net_baseline = None
|
||||
_net_baseline_lock = __import__("threading").Lock()
|
||||
|
||||
|
||||
def _get_net_delta():
|
||||
global _net_baseline
|
||||
try:
|
||||
net = psutil.net_io_counters()
|
||||
current = {"bytes_sent": net.bytes_sent, "bytes_recv": net.bytes_recv}
|
||||
with _net_baseline_lock:
|
||||
if _net_baseline is None:
|
||||
_net_baseline = copy.deepcopy(current)
|
||||
return {"bytes_sent": 0, "bytes_recv": 0}
|
||||
return {
|
||||
"bytes_sent": current["bytes_sent"] - _net_baseline["bytes_sent"],
|
||||
"bytes_recv": current["bytes_recv"] - _net_baseline["bytes_recv"],
|
||||
}
|
||||
except Exception:
|
||||
return {"bytes_sent": 0, "bytes_recv": 0}
|
||||
|
||||
|
||||
def _record_metrics():
|
||||
"""记录系统级指标。"""
|
||||
try:
|
||||
cpu = psutil.cpu_percent(interval=0.5)
|
||||
mem = psutil.virtual_memory()
|
||||
net = psutil.net_io_counters()
|
||||
net_delta = _get_net_delta()
|
||||
# 计算活跃连接
|
||||
with db.app.app_context():
|
||||
active = db.session.query(Connection).filter_by(
|
||||
@@ -32,8 +54,8 @@ def _record_metrics():
|
||||
"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,
|
||||
"net_bytes_sent": net_delta["bytes_sent"],
|
||||
"net_bytes_recv": net_delta["bytes_recv"],
|
||||
"active_connections": active,
|
||||
}
|
||||
with _metrics_lock:
|
||||
@@ -195,3 +217,34 @@ def prune_old_data():
|
||||
)
|
||||
db.session.commit()
|
||||
log.info("数据清理完成")
|
||||
|
||||
|
||||
def record_traffic_snapshot():
|
||||
"""记录每日流量快照(用于流量趋势图表)。"""
|
||||
from datetime import date
|
||||
today = date.today()
|
||||
with db.app.app_context():
|
||||
from sqlalchemy import func
|
||||
today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
result = db.session.query(
|
||||
func.coalesce(func.sum(Connection.bytes_in), 0),
|
||||
func.coalesce(func.sum(Connection.bytes_out), 0),
|
||||
func.count(Connection.id),
|
||||
).filter(Connection.started_at >= today_start).first()
|
||||
total_in = result[0] if result else 0
|
||||
total_out = result[1] if result else 0
|
||||
total_conns = result[2] if result else 0
|
||||
|
||||
snap = db.session.query(TrafficSnapshot).filter_by(date=today).first()
|
||||
if snap:
|
||||
snap.bytes_in = total_in
|
||||
snap.bytes_out = total_out
|
||||
snap.connections = total_conns
|
||||
else:
|
||||
snap = TrafficSnapshot(
|
||||
date=today, bytes_in=total_in, bytes_out=total_out,
|
||||
connections=total_conns,
|
||||
)
|
||||
db.session.add(snap)
|
||||
db.session.commit()
|
||||
log.info("流量快照已记录: %s", today)
|
||||
|
||||
@@ -69,7 +69,9 @@ class UserService:
|
||||
with db.app.app_context():
|
||||
if db.session.query(User).filter_by(username=username).first():
|
||||
return {"error": "用户名已存在"}
|
||||
u = User(username=username, password=password, **kwargs)
|
||||
u = User(username=username, **kwargs)
|
||||
if password:
|
||||
u.set_password(password)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
log.info("创建用户: %s", username)
|
||||
@@ -80,9 +82,12 @@ class UserService:
|
||||
u = db.session.query(User).get(uid)
|
||||
if not u:
|
||||
return {"error": "用户不存在"}
|
||||
password = kwargs.pop("password", None)
|
||||
for k, v in kwargs.items():
|
||||
if hasattr(u, k) and v is not None:
|
||||
setattr(u, k, v)
|
||||
if password:
|
||||
u.set_password(password)
|
||||
db.session.commit()
|
||||
log.info("更新用户: %s", u.username)
|
||||
return {"ok": True}
|
||||
|
||||
Reference in New Issue
Block a user