Files
PlantAI-dev/session_storage.py
T
2026-08-03 18:42:47 +08:00

399 lines
12 KiB
Python
Executable File

"""
会话存储模块 - 提供持久化存储功能
功能:
1. 用户会话历史保存到 SQLite 数据库
2. 活跃任务状态保存
3. 服务重启后能恢复会话
"""
import sqlite3
import json
import os
from datetime import datetime
from pathlib import Path
from typing import Optional, List, Dict, Any
# 默认数据库路径
DEFAULT_DB_PATH = str(Path(__file__).resolve().parent / "data" / "session_storage.db")
class SessionStorage:
"""会话存储管理器"""
def __init__(self, db_path: str = DEFAULT_DB_PATH):
self.db_path = db_path
self._ensure_db_dir()
self._init_tables()
def _ensure_db_dir(self):
"""确保数据库目录存在"""
db_dir = os.path.dirname(self.db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
def _get_connection(self) -> sqlite3.Connection:
"""获取数据库连接"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
return conn
def _init_tables(self):
"""初始化数据库表"""
conn = self._get_connection()
cursor = conn.cursor()
# 用户会话历史表
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
task_id TEXT NOT NULL,
job_id TEXT NOT NULL,
prefix TEXT NOT NULL,
stage TEXT NOT NULL,
status TEXT NOT NULL,
timestamp TEXT NOT NULL,
final_members INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
# 用户活跃任务表
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_active_jobs (
user_id TEXT PRIMARY KEY,
job_id TEXT NOT NULL,
prefix TEXT NOT NULL,
created_at TEXT NOT NULL,
stages TEXT, -- JSON 格式存储
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
# 运行中任务表
cursor.execute('''
CREATE TABLE IF NOT EXISTS running_tasks (
task_id TEXT PRIMARY KEY,
job_id TEXT NOT NULL,
prefix TEXT NOT NULL,
stage TEXT NOT NULL,
status TEXT NOT NULL,
timestamp TEXT NOT NULL,
user_id TEXT NOT NULL,
final_members INTEGER,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建索引
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id
ON user_sessions(user_id)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_user_sessions_timestamp
ON user_sessions(timestamp)
''')
conn.commit()
conn.close()
# ==================== 用户会话历史 ====================
def get_user_history(self, user_id: str, limit: int = 10) -> List[Dict[str, Any]]:
"""获取用户历史任务"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT task_id, job_id, prefix, stage, status, timestamp, final_members
FROM user_sessions
WHERE user_id = ?
ORDER BY timestamp DESC
LIMIT ?
''', (user_id, limit))
rows = cursor.fetchall()
conn.close()
return [dict(row) for row in reversed(rows)] # 反转回时间正序
def add_user_task(self, user_id: str, task_info: Dict[str, Any]):
"""添加任务到用户历史"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO user_sessions
(user_id, task_id, job_id, prefix, stage, status, timestamp, final_members)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
user_id,
task_info.get('task_id', ''),
task_info.get('job_id', ''),
task_info.get('prefix', ''),
task_info.get('stage', ''),
task_info.get('status', ''),
task_info.get('timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
task_info.get('final_members')
))
conn.commit()
conn.close()
def update_task_status(self, user_id: str, task_id: str, status: str,
final_members: Optional[int] = None):
"""更新任务状态"""
conn = self._get_connection()
cursor = conn.cursor()
if final_members is not None:
cursor.execute('''
UPDATE user_sessions
SET status = ?, final_members = ?
WHERE user_id = ? AND task_id = ?
''', (status, final_members, user_id, task_id))
else:
cursor.execute('''
UPDATE user_sessions
SET status = ?
WHERE user_id = ? AND task_id = ?
''', (status, user_id, task_id))
conn.commit()
conn.close()
def get_latest_task(self, user_id: str) -> Optional[Dict[str, Any]]:
"""获取用户最近一个任务"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT task_id, job_id, prefix, stage, status, timestamp, final_members
FROM user_sessions
WHERE user_id = ?
ORDER BY timestamp DESC
LIMIT 1
''', (user_id,))
row = cursor.fetchone()
conn.close()
return dict(row) if row else None
# ==================== 用户活跃任务 ====================
def get_user_active_job(self, user_id: str) -> Optional[Dict[str, Any]]:
"""获取用户当前活跃 job"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT user_id, job_id, prefix, created_at, stages
FROM user_active_jobs
WHERE user_id = ?
''', (user_id,))
row = cursor.fetchone()
conn.close()
if row:
job = dict(row)
job['stages'] = json.loads(job['stages'] or '{}')
return job
return None
def set_user_active_job(self, user_id: str, job_info: Dict[str, Any]):
"""设置用户当前活跃 job"""
conn = self._get_connection()
cursor = conn.cursor()
stages_json = json.dumps(job_info.get('stages', {}))
cursor.execute('''
INSERT OR REPLACE INTO user_active_jobs
(user_id, job_id, prefix, created_at, stages, updated_at)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
''', (
user_id,
job_info.get('job_id', ''),
job_info.get('prefix', ''),
job_info.get('created_at', datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
stages_json
))
conn.commit()
conn.close()
def update_job_stage(self, user_id: str, stage: str, status: str):
"""更新 job 的阶段状态"""
conn = self._get_connection()
cursor = conn.cursor()
# 先获取当前 stages
cursor.execute('''
SELECT stages FROM user_active_jobs WHERE user_id = ?
''', (user_id,))
row = cursor.fetchone()
if row:
stages = json.loads(row['stages'] or '{}')
stages[stage] = status
cursor.execute('''
UPDATE user_active_jobs
SET stages = ?, updated_at = CURRENT_TIMESTAMP
WHERE user_id = ?
''', (json.dumps(stages), user_id))
conn.commit()
conn.close()
def clear_user_active_job(self, user_id: str):
"""清除用户活跃 job"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
DELETE FROM user_active_jobs WHERE user_id = ?
''', (user_id,))
conn.commit()
conn.close()
# ==================== 运行中任务 ====================
def get_running_tasks(self) -> Dict[str, Dict[str, Any]]:
"""获取所有运行中任务"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT task_id, job_id, prefix, stage, status, timestamp, user_id, final_members
FROM running_tasks
''')
rows = cursor.fetchall()
conn.close()
return {row['task_id']: dict(row) for row in rows}
def add_running_task(self, task_id: str, task_info: Dict[str, Any]):
"""添加运行中任务"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO running_tasks
(task_id, job_id, prefix, stage, status, timestamp, user_id, final_members)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
task_id,
task_info.get('job_id', ''),
task_info.get('prefix', ''),
task_info.get('stage', ''),
task_info.get('status', ''),
task_info.get('timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
task_info.get('user_id', ''),
task_info.get('final_members')
))
conn.commit()
conn.close()
def update_running_task(self, task_id: str, status: str,
final_members: Optional[int] = None):
"""更新运行中任务状态"""
conn = self._get_connection()
cursor = conn.cursor()
if final_members is not None:
cursor.execute('''
UPDATE running_tasks
SET status = ?, final_members = ?, updated_at = CURRENT_TIMESTAMP
WHERE task_id = ?
''', (status, final_members, task_id))
else:
cursor.execute('''
UPDATE running_tasks
SET status = ?, updated_at = CURRENT_TIMESTAMP
WHERE task_id = ?
''', (status, task_id))
conn.commit()
conn.close()
def remove_running_task(self, task_id: str):
"""移除运行中任务"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
DELETE FROM running_tasks WHERE task_id = ?
''', (task_id,))
conn.commit()
conn.close()
# ==================== 统计和清理 ====================
def get_user_stats(self, user_id: str) -> Dict[str, int]:
"""获取用户任务统计"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT status, COUNT(*) as count
FROM user_sessions
WHERE user_id = ?
GROUP BY status
''', (user_id,))
rows = cursor.fetchall()
conn.close()
stats = {'running': 0, 'completed': 0, 'failed': 0}
for row in rows:
stats[row['status']] = row['count']
return stats
def cleanup_old_sessions(self, days: int = 30):
"""清理旧会话数据"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute('''
DELETE FROM user_sessions
WHERE created_at < datetime('now', '-{} days')
'''.format(days))
deleted = cursor.rowcount
conn.commit()
conn.close()
return deleted
def close(self):
"""关闭存储(SQLite 不需要显式关闭,但保留接口一致性)"""
pass
# 全局存储实例
_storage_instance: Optional[SessionStorage] = None
def get_storage() -> SessionStorage:
"""获取全局存储实例"""
global _storage_instance
if _storage_instance is None:
_storage_instance = SessionStorage()
return _storage_instance
def init_storage(db_path: str = DEFAULT_DB_PATH) -> SessionStorage:
"""初始化存储并返回实例"""
global _storage_instance
_storage_instance = SessionStorage(db_path)
return _storage_instance