Repository navigation
Expand file tree
/
Copy pathsession_manager.py
More file actions
159 lines (129 loc) · 5.12 KB
/
Copy pathsession_manager.py
File metadata and controls
159 lines (129 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""用户会话状态管理,用于交互菜单。"""
import asyncio
import time
from typing import Any
class SessionManager:
"""用户会话状态管理,支持自动过期。使用 asyncio.Lock 保证线程安全。"""
def __init__(self, timeout_seconds: int = 300, max_sessions: int = 1000):
"""初始化会话管理器。
Args:
timeout_seconds: 会话过期时间(秒),默认 5 分钟
max_sessions: 最大并发会话数,默认 1000
"""
self._sessions: dict[int, dict[str, Any]] = {}
self._timeout = timeout_seconds
self._max_sessions = max_sessions
self._lock = asyncio.Lock()
self._expired_users: set[int] = set() # Track users whose sessions expired
async def set_state(self, user_id: int, state: str, **kwargs) -> bool:
"""设置用户会话状态。
Args:
user_id: QQ 用户 ID
state: 状态标识(如 "awaiting_keyword"、"menu_status")
**kwargs: 附加上下文数据(如 group_id、action)
Returns:
设置成功返回 True,超过最大会话数返回 False
"""
async with self._lock:
if len(self._sessions) >= self._max_sessions and user_id not in self._sessions:
return False
self._sessions[user_id] = {
"state": state,
"expire": time.time() + self._timeout,
**kwargs
}
# Clear expired flag when user starts new session
self._expired_users.discard(user_id)
return True
async def get_state(self, user_id: int) -> dict[str, Any] | None:
"""获取用户会话状态(未过期时)。
Args:
user_id: QQ 用户 ID
Returns:
会话字典,过期或不存在返回 None
"""
async with self._lock:
session = self._sessions.get(user_id)
if not session:
return None
if time.time() > session["expire"]:
del self._sessions[user_id]
self._expired_users.add(user_id) # Mark as expired
return None
return session.copy() # Return copy to prevent external modification
async def clear_state(self, user_id: int) -> None:
"""清除用户会话状态。
Args:
user_id: QQ 用户 ID
"""
async with self._lock:
self._sessions.pop(user_id, None)
self._expired_users.discard(user_id)
async def check_and_clear_expired_flag(self, user_id: int) -> bool:
"""检查用户会话是否已过期并清除标记。
Args:
user_id: QQ 用户 ID
Returns:
会话已过期返回 True,否则返回 False
"""
async with self._lock:
if user_id in self._expired_users:
self._expired_users.discard(user_id)
return True
return False
async def get_state_or_check_expired(self, user_id: int) -> tuple[dict[str, Any] | None, bool]:
"""原子操作:获取会话状态或检查是否已过期。
合并 get_state 和 check_and_clear_expired_flag 为单次原子操作,
避免 TOCTOU 竞态条件。
Args:
user_id: QQ 用户 ID
Returns:
(会话或None, 是否已过期)。
会话有效: (session, False)
会话已过期: (None, True)
无会话: (None, False)
"""
async with self._lock:
session = self._sessions.get(user_id)
if session:
if time.time() > session["expire"]:
del self._sessions[user_id]
self._expired_users.discard(user_id)
return None, True
return session.copy(), False
if user_id in self._expired_users:
self._expired_users.discard(user_id)
return None, True
return None, False
async def cleanup_expired(self) -> int:
"""清理所有过期会话。
Returns:
清理的会话数量
"""
async with self._lock:
now = time.time()
expired = [uid for uid, sess in self._sessions.items() if now > sess["expire"]]
for uid in expired:
del self._sessions[uid]
self._expired_users.add(uid)
return len(expired)
async def extend_session(self, user_id: int) -> bool:
"""延长会话过期时间。
Args:
user_id: QQ 用户 ID
Returns:
延长成功返回 True,会话不存在返回 False
"""
async with self._lock:
session = self._sessions.get(user_id)
if session:
session["expire"] = time.time() + self._timeout
return True
return False
async def get_session_count(self) -> int:
"""获取当前活跃会话数。
Returns:
活跃会话数量
"""
async with self._lock:
return len(self._sessions)