添加总人数显示功能
This commit is contained in:
@@ -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;'> | </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]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user