Optimize database operations within loops (#1177)

* Optimize database operations within loops

* Optimize opera log
This commit is contained in:
Wu Clan
2026-05-14 11:38:38 +08:00
committed by GitHub
parent e32b4232c5
commit 5352f98c13
19 changed files with 227 additions and 83 deletions
+13 -3
View File
@@ -21,6 +21,8 @@ async def get_sessions(
token_keys = await redis_client.get_prefix(f'{settings.TOKEN_REDIS_PREFIX}:*')
online_clients = await redis_client.smembers(settings.TOKEN_ONLINE_REDIS_PREFIX)
data: list[GetTokenDetail] = []
if not token_keys:
return response_base.success(data=data)
def append_token_detail() -> None:
data.append(
@@ -37,8 +39,12 @@ async def get_sessions(
),
)
for key in token_keys:
token = await redis_client.get(key)
token_values = await redis_client.mget(*token_keys)
token_details: list[GetTokenDetail] = []
extra_info_keys: list[str] = []
for token in token_values:
if not token:
continue
token_payload = jwt_decode(token)
user_id = token_payload.user_id
session_uuid = token_payload.session_uuid
@@ -55,7 +61,11 @@ async def get_sessions(
last_login_time='未知',
expire_time=token_payload.expire_time,
)
extra_info = await redis_client.get(f'{settings.TOKEN_EXTRA_INFO_REDIS_PREFIX}:{user_id}:{session_uuid}')
token_details.append(token_detail)
extra_info_keys.append(f'{settings.TOKEN_EXTRA_INFO_REDIS_PREFIX}:{user_id}:{session_uuid}')
extra_infos = await redis_client.mget(*extra_info_keys) if extra_info_keys else []
for token_detail, extra_info in zip(token_details, extra_infos, strict=True):
if extra_info:
extra_info = json.loads(extra_info)
# 排除 swagger 登录生成的 token
+10
View File
@@ -54,6 +54,16 @@ class CRUDDataRule(CRUDPlus[DataRule]):
"""
return await self.select_models(db)
async def get_all_by_ids(self, db: AsyncSession, pks: list[int]) -> Sequence[DataRule]:
"""
通过 ID 列表批量获取数据规则
:param db: 数据库会话
:param pks: 规则 ID 列表
:return:
"""
return await self.select_models(db, id__in=pks)
async def create(self, db: AsyncSession, obj: CreateDataRuleParam) -> None:
"""
创建规则
+10
View File
@@ -66,6 +66,16 @@ class CRUDDataScope(CRUDPlus[DataScope]):
"""
return await self.select_models(db)
async def get_all_by_ids(self, db: AsyncSession, pks: list[int]) -> Sequence[DataScope]:
"""
通过 ID 列表批量获取数据范围
:param db: 数据库会话
:param pks: 范围 ID 列表
:return:
"""
return await self.select_models(db, id__in=pks)
async def get_select(self, name: str | None, status: int | None) -> Select:
"""
获取数据范围列表查询表达式
+10
View File
@@ -64,6 +64,16 @@ class CRUDMenu(CRUDPlus[Menu]):
return await self.select_models_order(db, 'sort', 'asc', **filters)
async def get_all_by_ids(self, db: AsyncSession, menu_ids: list[int]) -> Sequence[Menu]:
"""
通过 ID 列表批量获取菜单
:param db: 数据库会话
:param menu_ids: 菜单 ID 列表
:return:
"""
return await self.select_models(db, id__in=menu_ids)
async def create(self, db: AsyncSession, obj: CreateMenuParam) -> None:
"""
创建菜单
+10
View File
@@ -73,6 +73,16 @@ class CRUDRole(CRUDPlus[Role]):
"""
return await self.select_models(db)
async def get_all_by_ids(self, db: AsyncSession, role_ids: list[int]) -> Sequence[Role]:
"""
通过 ID 列表批量获取角色
:param db: 数据库会话
:param role_ids: 角色 ID 列表
:return:
"""
return await self.select_models(db, id__in=role_ids)
async def get_select(self, name: str | None, status: int | None) -> Select:
"""
获取角色列表查询表达式
+11
View File
@@ -1,3 +1,4 @@
from collections.abc import Sequence
from typing import Any
import bcrypt
@@ -55,6 +56,16 @@ class CRUDUser(CRUDPlus[User]):
"""
return await self.select_model_by_column(db, username=username)
async def get_all_by_usernames(self, db: AsyncSession, usernames: list[str]) -> Sequence[User]:
"""
通过用户名列表批量获取用户
:param db: 数据库会话
:param usernames: 用户名列表
:return:
"""
return await self.select_models(db, username__in=usernames)
async def get_by_nickname(self, db: AsyncSession, nickname: str) -> User | None:
"""
通过昵称获取用户
+2 -3
View File
@@ -218,10 +218,9 @@ class AuthService:
raise errors.NotFoundError(msg='用户不存在')
if not user.status:
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
token_keys = await redis_client.get_prefix(f'{settings.TOKEN_REDIS_PREFIX}:{user.id}:*')
if not user.is_multi_login and [
key
for key in await redis_client.get_prefix(f'{settings.TOKEN_REDIS_PREFIX}:{user.id}:*')
if not key.endswith(f':{token_payload.session_uuid}')
key for key in token_keys if not key.endswith(f':{token_payload.session_uuid}')
]:
raise errors.ForbiddenError(msg='此用户已在异地登录,请重新登录并及时修改密码')
new_token = await create_new_token(
@@ -121,9 +121,9 @@ class DataScopeService:
data_scope = await data_scope_dao.get(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
for rule_id in rule_ids.rules:
rule = await data_rule_dao.get(db, rule_id)
if not rule:
if rule_ids.rules:
rules = await data_rule_dao.get_all_by_ids(db, list(set(rule_ids.rules)))
if {rule.id for rule in rules} != set(rule_ids.rules):
raise errors.NotFoundError(msg='数据规则不存在')
count = await data_scope_dao.update_rules(db, pk, rule_ids)
await user_cache_manager.clear_by_data_scope_id(db, [pk])
+3 -2
View File
@@ -27,12 +27,13 @@ class PluginService:
"""获取所有插件"""
changed_key = f'{settings.PLUGIN_REDIS_PREFIX}:changed'
keys = [key async for key in redis_client.scan_iter(f'{settings.PLUGIN_REDIS_PREFIX}:*') if key != changed_key]
keys = [key for key in await redis_client.get_prefix(f'{settings.PLUGIN_REDIS_PREFIX}:') if key != changed_key]
if not keys:
return []
result = []
for info in await redis_client.mget(*keys):
plugin_infos = await redis_client.mget(*keys)
for info in plugin_infos:
if info is None:
continue
+6 -6
View File
@@ -145,9 +145,9 @@ class RoleService:
role = await role_dao.get(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
for menu_id in menu_ids.menus:
menu = await menu_dao.get(db, menu_id)
if not menu:
if menu_ids.menus:
menus = await menu_dao.get_all_by_ids(db, list(set(menu_ids.menus)))
if {menu.id for menu in menus} != set(menu_ids.menus):
raise errors.NotFoundError(msg='菜单不存在')
count = await role_dao.update_menus(db, pk, menu_ids)
await user_cache_manager.clear_by_role_id(db, [pk])
@@ -167,9 +167,9 @@ class RoleService:
role = await role_dao.get(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
for scope_id in scope_ids.scopes:
scope = await data_scope_dao.get(db, scope_id)
if not scope:
if scope_ids.scopes:
scopes = await data_scope_dao.get_all_by_ids(db, list(set(scope_ids.scopes)))
if {scope.id for scope in scopes} != set(scope_ids.scopes):
raise errors.NotFoundError(msg='数据范围不存在')
count = await role_dao.update_scopes(db, pk, scope_ids)
await user_cache_manager.clear_by_role_id(db, [pk])
+15 -27
View File
@@ -94,8 +94,9 @@ class UserService:
raise errors.RequestError(msg='密码不允许为空')
if not await dept_dao.get(db, obj.dept_id):
raise errors.NotFoundError(msg='部门不存在')
for role_id in obj.roles:
if not await role_dao.get(db, role_id):
if obj.roles:
roles = await role_dao.get_all_by_ids(db, list(set(obj.roles)))
if {role.id for role in roles} != set(obj.roles):
raise errors.NotFoundError(msg='角色不存在')
obj.nickname = obj.nickname or obj.username
await user_dao.add(db, obj)
@@ -117,8 +118,9 @@ class UserService:
raise errors.ConflictError(msg='用户名已注册')
if obj.dept_id and obj.dept_id != user.dept_id and not await dept_dao.get(db, dept_id=obj.dept_id):
raise errors.NotFoundError(msg='部门不存在')
for role_id in obj.roles:
if not await role_dao.get(db, role_id):
if obj.roles:
roles = await role_dao.get_all_by_ids(db, list(set(obj.roles)))
if {role.id for role in roles} != set(obj.roles):
raise errors.NotFoundError(msg='角色不存在')
count = await user_dao.update(db, user.id, obj)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
@@ -205,14 +207,9 @@ class UserService:
history_obj = CreateUserPasswordHistoryParam(user_id=user.id, password=user.password)
await password_security_service.save_password_history(db, history_obj)
await user_dao.update_password_changed_time(db, user.id)
key_prefix = [
f'{settings.TOKEN_REDIS_PREFIX}:{user.id}',
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}',
f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}',
]
for prefix in key_prefix:
await redis_client.delete_prefix(prefix)
await redis_client.delete_prefix(f'{settings.TOKEN_REDIS_PREFIX}:{user.id}')
await redis_client.delete_prefix(f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}')
await redis_client.delete_prefix(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
@@ -288,14 +285,9 @@ class UserService:
history_obj = CreateUserPasswordHistoryParam(user_id=user.id, password=user.password)
await password_security_service.save_password_history(db, history_obj)
await user_dao.update_password_changed_time(db, user.id)
key_prefix = [
f'{settings.TOKEN_REDIS_PREFIX}:{user_id}',
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user_id}',
f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}',
]
for prefix in key_prefix:
await redis_client.delete_prefix(prefix)
await redis_client.delete_prefix(f'{settings.TOKEN_REDIS_PREFIX}:{user_id}')
await redis_client.delete_prefix(f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user_id}')
await redis_client.delete_prefix(f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}')
return count
@staticmethod
@@ -311,13 +303,9 @@ class UserService:
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.delete(db, user.id)
key_prefix = [
f'{settings.TOKEN_REDIS_PREFIX}:{user.id}',
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}',
f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}',
]
for key in key_prefix:
await redis_client.delete_prefix(key)
await redis_client.delete_prefix(f'{settings.TOKEN_REDIS_PREFIX}:{user.id}')
await redis_client.delete_prefix(f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}')
await redis_client.delete_prefix(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count