添加总人数显示功能

This commit is contained in:
2025-10-19 23:09:24 +08:00
parent b80391415b
commit 071e4da378
7 changed files with 389 additions and 324 deletions
+106 -9
View File
@@ -68,13 +68,79 @@ import os
import time
from datetime import datetime
from threading import Lock
import atexit
# ==================== 在线人数统计 ====================
# 全局变量:追踪活跃用户
active_users = {} # 存储用户最后活跃时间 {session_id: timestamp}
users_lock = Lock() # 线程锁,保证并发安全
"""
==================== 在线/总人数统计与持久化 ====================
新增:在项目根目录下自动创建数据文件夹,持久化累计人数,
防止容器重启后总人数从 0 开始。
保存策略:每 SAVE_INTERVAL_SECONDS 秒写盘一次(由在线人数刷新触发)。
"""
# 当前活跃用户(在线)
active_users = {} # {session_id: last_active_timestamp}
users_lock = Lock()
TIMEOUT_SECONDS = 300 # 5分钟无活动视为离线
# 累计人数(总人数)持久化
stats_lock = Lock()
STATS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data_usage")
STATS_FILE = os.path.join(STATS_DIR, "usage_stats.json")
SAVE_INTERVAL_SECONDS = int(os.environ.get("USAGE_SAVE_INTERVAL", "60")) # 默认60秒写盘一次
total_users = 0 # 累计会话人数(近似代表累计访问人数)
seen_sessions = set() # 仅在本次进程内去重,避免同一session重复计数
_LAST_SAVE_TS = 0.0
def _ensure_stats_dir():
try:
os.makedirs(STATS_DIR, exist_ok=True)
except Exception:
# 即便目录创建失败,不影响主流程,仅导致不持久化
pass
def _load_usage_stats():
"""从磁盘加载累计人数,若无文件则从0开始。"""
global total_users, _LAST_SAVE_TS
try:
if os.path.exists(STATS_FILE):
with open(STATS_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
total_users = int(data.get("total_users", 0))
_LAST_SAVE_TS = float(data.get("last_saved_at", time.time()))
else:
total_users = 0
_LAST_SAVE_TS = time.time()
except Exception:
# 文件损坏或解析失败时,安全回退为0
total_users = 0
_LAST_SAVE_TS = time.time()
def _save_usage_stats():
"""将累计人数持久化到磁盘。"""
global _LAST_SAVE_TS
try:
tmp_path = STATS_FILE + ".tmp"
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump({"total_users": total_users, "last_saved_at": time.time()}, f, ensure_ascii=False, indent=2)
os.replace(tmp_path, STATS_FILE)
_LAST_SAVE_TS = time.time()
except Exception:
# 写盘失败不阻塞主流程
pass
def _maybe_save_usage_stats():
"""按间隔条件触发写盘。调用点:在线人数UI刷新时。"""
try:
with stats_lock:
if time.time() - _LAST_SAVE_TS >= SAVE_INTERVAL_SECONDS:
_save_usage_stats()
except Exception:
pass
# 初始化目录与读取历史累计值
_ensure_stats_dir()
_load_usage_stats()
atexit.register(_save_usage_stats)
def get_active_users_count():
"""获取当前活跃用户数量"""
current_time = time.time()
@@ -87,15 +153,34 @@ def get_active_users_count():
return len(active_users)
def update_user_activity(session_id):
"""更新用户活跃时间"""
"""更新用户活跃时间并在首次出现时累计总人数。"""
# 更新在线用户心跳
with users_lock:
active_users[session_id] = time.time()
# 首次见到该 session,累计 +1(仅在本进程内去重),并等待定时写盘
try:
with stats_lock:
global total_users
if session_id not in seen_sessions:
seen_sessions.add(session_id)
total_users += 1
except Exception:
# 统计异常不影响主流程
pass
def get_online_status_html():
"""生成在线人数显示HTML"""
"""生成在线人数与总人数的显示HTML,并按需触发持久化。"""
count = get_active_users_count()
# 读取累计人数(读锁即可)
try:
with stats_lock:
total = int(total_users)
except Exception:
total = 0
# 触发间隔写盘
_maybe_save_usage_stats()
return f"""
<div style='display: inline-flex; align-items: center; gap: 8px;
<div style='display: inline-flex; align-items: center; gap: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 8px 16px; border-radius: 20px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);'>
@@ -104,6 +189,12 @@ def get_online_status_html():
<span style='color: #fff; font-weight: 700; font-size: 1.2em;
background: rgba(255,255,255,0.2); padding: 2px 12px;
border-radius: 12px; min-width: 30px; text-align: center;'>{count}</span>
<span style='opacity: 0.4; color: #fff;'>&nbsp;|&nbsp;</span>
<span style='font-size: 1.2em;'>👥</span>
<span style='color: white; font-weight: 600; font-size: 1em;'>总人数:</span>
<span style='color: #fff; font-weight: 700; font-size: 1.2em;
background: rgba(255,255,255,0.2); padding: 2px 12px;
border-radius: 12px; min-width: 30px; text-align: center;'>{total}</span>
</div>
"""
# ==================================================
@@ -2210,9 +2301,15 @@ with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=cus
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, rl_plot_output, rl_poles_display, k_number_display, online_counter]
)
# 页面加载时初始化在线人数
# 页面加载时:记录一次活跃,计入总人数,并初始化在线人数
def _on_page_load(sid):
try:
update_user_activity(sid)
finally:
return (get_online_status_html(),)
demo.load(
fn=lambda sid: (get_online_status_html(),),
fn=_on_page_load,
inputs=[session_id],
outputs=[online_counter]
)