checkpoint: 权限即时生效——角色/用户变更自动踢下线 + roleless 用户自读修复
- online/service: kick_user_sessions 扫描 USER_SESSION 匹配目标用户,删三键使登录时固化的权限快照即时失效 - role service+controller: 更新/删除/改状态/改权限后踢下线绑定该角色的用户 - user service+controller: 停用/改密等变更后踢下线(对应用户即时 401 重登) - permission: 无角色用户读自身记录时按 id 匹配放行(修复 409 真 bug)
This commit is contained in:
@@ -78,6 +78,52 @@ class OnlineService:
|
||||
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.USER_SESSION.key}:{session_id}")
|
||||
logger.info(f"强制下线用户会话: {session_id}")
|
||||
|
||||
@staticmethod
|
||||
async def kick_user_sessions(redis: Redis, user_ids: list[int]) -> int:
|
||||
"""强制下线指定用户的全部在线会话(权限变更/停用/改密后自动踢人)。
|
||||
|
||||
扫描 USER_SESSION:{session_id} 匹配 user_id,删除该会话的
|
||||
ACCESS_TOKEN / REFRESH_TOKEN / USER_SESSION 三键,使登录时固化的
|
||||
权限快照即时失效(用户下次请求 401,重新登录拿新权限)。
|
||||
返回踢下线会话数;redis 不可用时仅记日志,不影响业务主流程。
|
||||
"""
|
||||
if not user_ids:
|
||||
return 0
|
||||
target = {int(uid) for uid in user_ids}
|
||||
keys = await RedisCURD(redis).scan_keys(f"{RedisInitKeyConfig.USER_SESSION.key}:*")
|
||||
if not keys:
|
||||
return 0
|
||||
raws = await RedisCURD(redis).mget(keys)
|
||||
session_ids: list[str] = []
|
||||
for key, raw in zip(keys, raws, strict=False):
|
||||
if not raw:
|
||||
continue
|
||||
try:
|
||||
info = json.loads(raw)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
continue
|
||||
try:
|
||||
uid = int(info.get("user_id"))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if uid in target:
|
||||
key_str = key.decode() if isinstance(key, bytes) else key
|
||||
session_ids.append(key_str.split(":")[-1])
|
||||
if not session_ids:
|
||||
return 0
|
||||
delete_keys: list[str] = []
|
||||
for sid in session_ids:
|
||||
delete_keys.extend(
|
||||
[
|
||||
f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{sid}",
|
||||
f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:{sid}",
|
||||
f"{RedisInitKeyConfig.USER_SESSION.key}:{sid}",
|
||||
]
|
||||
)
|
||||
await RedisCURD(redis).delete(*delete_keys)
|
||||
logger.info(f"权限变更自动踢下线: user_ids={sorted(target)} sessions={len(session_ids)}")
|
||||
return len(session_ids)
|
||||
|
||||
@staticmethod
|
||||
async def clear_online(redis: Redis) -> None:
|
||||
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*")
|
||||
|
||||
Reference in New Issue
Block a user