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:
@@ -4,6 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.base_schema import AuthSchema, BatchSetAvailable, PageResultSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import logger
|
||||
from app.utils.common_util import search_to_dict
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
|
||||
@@ -110,12 +111,13 @@ class RoleService:
|
||||
new_role = await RoleCRUD(self.auth, self.db).create(data=data)
|
||||
return await self.detail(id=new_role.id)
|
||||
|
||||
async def update(self, id: int, data: RoleUpdateSchema) -> RoleOutSchema:
|
||||
async def update(self, id: int, data: RoleUpdateSchema, redis: Any | None = None) -> RoleOutSchema:
|
||||
"""更新角色
|
||||
|
||||
参数:
|
||||
- id (int): 角色ID
|
||||
- data (RoleUpdateSchema): 更新角色模型
|
||||
- redis (Any | None): Redis 客户端(非 None 时踢下线绑定该角色的用户)
|
||||
|
||||
返回:
|
||||
- RoleOutSchema: 更新后的角色响应模型
|
||||
@@ -128,13 +130,16 @@ class RoleService:
|
||||
if exist_code and exist_code.id != id:
|
||||
raise CustomException(msg="更新失败,角色编码已存在")
|
||||
await RoleCRUD(self.auth, self.db).update(id=id, data=data)
|
||||
if redis is not None:
|
||||
await self._kick_users(redis, [id])
|
||||
return await self.detail(id=id)
|
||||
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
async def delete(self, ids: list[int], redis: Any | None = None) -> None:
|
||||
"""删除角色
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 角色ID列表
|
||||
- redis (Any | None): Redis 客户端(非 None 时踢下线绑定该角色的用户)
|
||||
|
||||
返回:
|
||||
- None
|
||||
@@ -148,12 +153,15 @@ class RoleService:
|
||||
raise CustomException(msg="删除失败,部分ID不存在")
|
||||
|
||||
await RoleCRUD(self.auth, self.db).delete(ids=ids)
|
||||
if redis is not None:
|
||||
await self._kick_users(redis, ids)
|
||||
|
||||
async def set_permission(self, data: RolePermissionSettingSchema) -> None:
|
||||
async def set_permission(self, data: RolePermissionSettingSchema, redis: Any | None = None) -> None:
|
||||
"""设置角色权限
|
||||
|
||||
参数:
|
||||
- data (RolePermissionSettingSchema): 角色权限设置模型
|
||||
- redis (Any | None): Redis 客户端(非 None 时踢下线绑定角色的用户)
|
||||
|
||||
返回:
|
||||
- None
|
||||
@@ -164,12 +172,15 @@ class RoleService:
|
||||
# 设置数据权限范围(自定义部门关联已废弃,直接清空)
|
||||
await RoleCRUD(self.auth, self.db).set(ids=data.role_ids, data_scope=data.data_scope)
|
||||
await RoleCRUD(self.auth, self.db).set_role_depts_crud(role_ids=data.role_ids, dept_ids=[])
|
||||
if redis is not None:
|
||||
await self._kick_users(redis, data.role_ids)
|
||||
|
||||
async def set_available(self, data: BatchSetAvailable) -> None:
|
||||
async def set_available(self, data: BatchSetAvailable, redis: Any | None = None) -> None:
|
||||
"""设置角色可用状态
|
||||
|
||||
参数:
|
||||
- data (BatchSetAvailable): 批量设置可用状态模型
|
||||
- redis (Any | None): Redis 客户端(非 None 时踢下线绑定该角色的用户)
|
||||
|
||||
返回:
|
||||
- None
|
||||
@@ -180,6 +191,23 @@ class RoleService:
|
||||
if rid not in role_map:
|
||||
raise CustomException(msg="该数据不存在")
|
||||
await RoleCRUD(self.auth, self.db).set(ids=data.ids, status=data.status)
|
||||
if redis is not None:
|
||||
await self._kick_users(redis, data.ids)
|
||||
|
||||
async def _kick_users(self, redis: Any, role_ids: list[int]) -> int:
|
||||
"""踢下线绑定指定角色的全部用户的会话(角色权限/状态/删除后调用)。"""
|
||||
roles = await RoleCRUD(self.auth, self.db).get_list(search={"id": ("in", role_ids)}, preload=["users"])
|
||||
user_ids = sorted({u.id for r in roles for u in r.users})
|
||||
if not user_ids:
|
||||
return 0
|
||||
|
||||
from app.api.v1.module_monitor.online.service import OnlineService
|
||||
|
||||
try:
|
||||
return await OnlineService.kick_user_sessions(redis=redis, user_ids=user_ids)
|
||||
except Exception as e:
|
||||
logger.error(f"角色变更自动踢下线失败: role_ids={role_ids}, err={e!s}")
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def export_list(role_list: list[dict[str, Any]]) -> bytes:
|
||||
|
||||
Reference in New Issue
Block a user