Files
AutoControlCourse/user_stats.py

109 lines
3.9 KiB
Python

import json
import os
import time
from threading import Lock
import atexit
# --- 配置 ---
TIMEOUT_SECONDS = 300 # 5分钟无活动视为离线
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"))
# --- 全局变量 ---
active_users = {} # {session_id: last_active_timestamp}
users_lock = Lock()
stats_lock = Lock()
total_users = 0
seen_sessions = set()
_LAST_SAVE_TS = 0.0
# --- 内部函数 ---
def _ensure_stats_dir():
try:
os.makedirs(STATS_DIR, exist_ok=True)
except OSError:
pass
def _load_usage_stats():
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()))
except (IOError, json.JSONDecodeError):
total_users = 0
_LAST_SAVE_TS = time.time()
def _save_usage_stats():
global _LAST_SAVE_TS
try:
with stats_lock:
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 (IOError, OSError):
pass
def _maybe_save_usage_stats():
if time.time() - _LAST_SAVE_TS >= SAVE_INTERVAL_SECONDS:
_save_usage_stats()
# --- 外部接口 ---
def update_user_activity(session_id: str):
"""更新用户活跃时间并在首次出现时累计总人数。"""
current_time = time.time()
with users_lock:
active_users[session_id] = current_time
with stats_lock:
global total_users
if session_id not in seen_sessions:
seen_sessions.add(session_id)
total_users += 1
def get_active_users_count():
"""获取当前活跃用户数量"""
current_time = time.time()
with users_lock:
expired_users = [uid for uid, last_time in active_users.items() if current_time - last_time > TIMEOUT_SECONDS]
for uid in expired_users:
del active_users[uid]
return len(active_users)
def get_online_status_html():
"""生成在线人数与总人数的显示HTML,并按需触发持久化。"""
count = get_active_users_count()
with stats_lock:
total = total_users
_maybe_save_usage_stats()
return f"""
<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);'>
<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;'>{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>
"""
# --- 初始化 ---
_ensure_stats_dir()
_load_usage_stats()
atexit.register(_save_usage_stats) # 确保程序退出时保存数据