mirror of
https://github.com/fastapi-practices/fastapi-best-architecture.git
synced 2026-09-21 21:15:13 +00:00
Optimize database operations within loops (#1177)
* Optimize database operations within loops * Optimize opera log
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
创建规则
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
获取数据范围列表查询表达式
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
创建菜单
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
获取角色列表查询表达式
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
通过昵称获取用户
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user