Browse Source

fix: 修复菜单挂载到根目录时路由加载异常等一系列相关问题

master
insistence 12 months ago
parent
commit
43f903c6e0
  1. 32
      ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py
  2. 229
      ruoyi-fastapi-backend/module_admin/service/login_service.py

32
ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py

@ -1,8 +1,9 @@
import re import re
from pydantic import BaseModel, ConfigDict, model_validator from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel from pydantic.alias_generators import to_camel
from typing import Optional from typing import Optional, List, Union
from exceptions.exception import ModelValidatorException from exceptions.exception import ModelValidatorException
from module_admin.entity.vo.menu_vo import MenuModel
class UserLogin(BaseModel): class UserLogin(BaseModel):
@ -52,4 +53,31 @@ class SmsCode(BaseModel):
is_success: Optional[bool] = None is_success: Optional[bool] = None
sms_code: str sms_code: str
session_id: str session_id: str
message: Optional[str] = None message: Optional[str] = None
class MenuTreeModel(MenuModel):
children: Optional[Union[List['MenuTreeModel'], None]] = None
class MetaModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
title: Optional[str] = None
icon: Optional[str] = None
no_cache: Optional[bool] = None
link: Optional[str] = None
class RouterModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
name: Optional[str] = None
path: Optional[str] = None
hidden: Optional[bool] = None
redirect: Optional[str] = None
component: Optional[str] = None
query: Optional[str] = None
always_show: Optional[bool] = None
meta: Optional[MetaModel] = None
children: Optional[Union[List['RouterModel'], None]] = None

229
ruoyi-fastapi-backend/module_admin/service/login_service.py

@ -24,6 +24,7 @@ class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
""" """
自定义OAuth2PasswordRequestForm类增加验证码及会话编号参数 自定义OAuth2PasswordRequestForm类增加验证码及会话编号参数
""" """
def __init__( def __init__(
self, self,
grant_type: str = Form(default=None, regex="password"), grant_type: str = Form(default=None, regex="password"),
@ -47,6 +48,7 @@ class LoginService:
""" """
登录模块服务层 登录模块服务层
""" """
@classmethod @classmethod
async def authenticate_user(cls, request: Request, query_db: AsyncSession, login_user: UserLogin): async def authenticate_user(cls, request: Request, query_db: AsyncSession, login_user: UserLogin):
""" """
@ -231,57 +233,95 @@ class LoginService:
""" """
query_user = await UserDao.get_user_by_id(query_db, user_id=user_id) query_user = await UserDao.get_user_by_id(query_db, user_id=user_id)
user_router_menu = sorted([row for row in query_user.get('user_menu_info') if row.menu_type in ['M', 'C']], key=lambda x: x.order_num) user_router_menu = sorted([row for row in query_user.get('user_menu_info') if row.menu_type in ['M', 'C']], key=lambda x: x.order_num)
user_router = cls.__generate_user_router_menu(0, user_router_menu) menus = cls.__generate_menus(0, user_router_menu)
return user_router user_router = cls.__generate_user_router_menu(menus)
return [router.model_dump(exclude_unset=True, by_alias=True) for router in user_router]
@classmethod @classmethod
def __generate_user_router_menu(cls, pid: int, permission_list): def __generate_menus(cls, pid: int, permission_list: List[SysMenu]):
""" """
工具方法根据菜单信息生成路由信息树形嵌套数据 工具方法根据菜单信息生成菜单信息树形嵌套数据
:param pid: 菜单id :param pid: 菜单id
:param permission_list: 菜单列表信息 :param permission_list: 菜单列表信息
:return: 路由信息树形嵌套数据 :return: 菜单信息树形嵌套数据
""" """
router_list = [] menu_list: List[MenuTreeModel] = []
for permission in permission_list: for permission in permission_list:
if permission.parent_id == pid: if permission.parent_id == pid:
children = cls.__generate_user_router_menu(permission.menu_id, permission_list) children = cls.__generate_menus(permission.menu_id, permission_list)
router_list_data = {} menu_list_data = MenuTreeModel(**CamelCaseUtil.transform_result(permission))
if permission.menu_type == 'M': if children:
router_list_data['name'] = permission.path.capitalize() menu_list_data.children = children
router_list_data['hidden'] = False if permission.visible == '0' else True menu_list.append(menu_list_data)
if permission.parent_id == 0:
router_list_data['component'] = 'Layout' return menu_list
router_list_data['path'] = f'/{permission.path}'
else: @classmethod
router_list_data['component'] = 'ParentView' def __generate_user_router_menu(cls, permission_list: List[MenuTreeModel]):
router_list_data['path'] = permission.path """
if permission.is_frame == 1: 工具方法根据菜单树信息生成路由信息树形嵌套数据
router_list_data['redirect'] = 'noRedirect' :param permission_list: 菜单树列表信息
else: :return: 路由信息树形嵌套数据
router_list_data['path'] = permission.path """
if children: router_list: List[RouterModel] = []
router_list_data['alwaysShow'] = True for permission in permission_list:
router_list_data['children'] = children router = RouterModel(
router_list_data['meta'] = { hidden=True if permission.visible == '1' else False,
'title': permission.menu_name, name=RouterUtil.get_router_name(permission),
'icon': permission.icon, path=RouterUtil.get_router_path(permission),
'noCache': False if permission.is_cache == '0' else True, component=RouterUtil.get_component(permission),
'link': permission.path if permission.is_frame == 0 else None query=permission.query,
} meta=MetaModel(
elif permission.menu_type == 'C': title=permission.menu_name,
router_list_data['name'] = permission.path.capitalize() icon=permission.icon,
router_list_data['path'] = permission.path noCache=True if permission.is_cache == 1 else False,
router_list_data['query'] = permission.query link=permission.path if RouterUtil.is_http(permission.path) else None
router_list_data['hidden'] = False if permission.visible == '0' else True )
router_list_data['component'] = permission.component )
router_list_data['meta'] = { c_menus = permission.children
'title': permission.menu_name, if c_menus and permission.menu_type == 'M':
'icon': permission.icon, router.always_show = True
'noCache': False if permission.is_cache == '0' else True, router.redirect = 'noRedirect'
'link': permission.path if permission.is_frame == 0 else None router.children = cls.__generate_user_router_menu(c_menus)
} elif RouterUtil.is_menu_frame(permission):
router_list.append(router_list_data) router.meta = None
children_list: List[RouterModel] = []
children = RouterModel(
path=permission.path,
component=permission.component,
name=permission.path.capitalize(),
meta=MetaModel(
title=permission.menu_name,
icon=permission.icon,
noCache=True if permission.is_cache == 1 else False,
link=permission.path if RouterUtil.is_http(permission.path) else None
),
query=permission.query
)
children_list.append(children)
router.children = children_list
elif permission.parent_id == 0 and RouterUtil.is_inner_link(permission):
router.meta = MetaModel(
title=permission.menu_name,
icon=permission.icon
)
router.path = '/'
children_list: List[RouterModel] = []
router_path = RouterUtil.inner_link_replace_each(permission.path)
children = RouterModel(
path=router_path,
component='InnerLink',
name=router_path.capitalize(),
meta=MetaModel(
title=permission.menu_name,
icon=permission.icon,
link=permission.path if RouterUtil.is_http(permission.path) else None
)
)
children_list.append(children)
router.children = children_list
router_list.append(router)
return router_list return router_list
@ -386,3 +426,106 @@ class LoginService:
# await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id') # await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id')
return True return True
class RouterUtil:
"""
路由处理工具类
"""
@classmethod
def get_router_name(cls, menu: MenuTreeModel):
"""
获取路由名称
:param menu: 菜单数对象
:return: 路由名称
"""
router_name = menu.path.capitalize()
if cls.is_menu_frame(menu):
router_name = ''
return router_name
@classmethod
def get_router_path(cls, menu: MenuTreeModel):
"""
获取路由地址
:param menu: 菜单数对象
:return: 路由地址
"""
# 内链打开外网方式
router_path = menu.path
if menu.parent_id != 0 and cls.is_inner_link(menu):
router_path = cls.inner_link_replace_each(router_path)
# 非外链并且是一级目录(类型为目录)
if menu.parent_id == 0 and menu.menu_type == 'M' and menu.is_frame == 1:
router_path = f'/{menu.path}'
# 非外链并且是一级目录(类型为菜单)
elif cls.is_menu_frame(menu):
router_path = '/'
return router_path
@classmethod
def get_component(cls, menu: MenuTreeModel):
"""
获取组件信息
:param menu: 菜单数对象
:return: 组件信息
"""
component = 'Layout'
if menu.component and not cls.is_menu_frame(menu):
component = menu.component
elif menu.component and menu.parent_id != 0 and cls.is_inner_link(menu):
component = 'InnerLink'
elif menu.component and cls.is_parent_view(menu):
component = 'ParentView'
return component
@classmethod
def is_menu_frame(cls, menu: MenuTreeModel):
"""
判断是否为菜单内部跳转
:param menu: 菜单数对象
:return: 是否为菜单内部跳转
"""
return menu.parent_id == 0 and menu.menu_type == 'C' and menu.is_frame == 1
@classmethod
def is_inner_link(cls, menu: MenuTreeModel):
"""
判断是否为内链组件
:param menu: 菜单数对象
:return: 是否为内链组件
"""
return menu.is_frame == 1 and cls.is_http(menu.path)
@classmethod
def is_parent_view(cls, menu: MenuTreeModel):
"""
判断是否为parent_view组件
:param menu: 菜单数对象
:return: 是否为parent_view组件
"""
return menu.parent_id != 0 and menu.menu_type == 'M'
@classmethod
def is_http(cls, link: str):
"""
判断是否为http(s)://开头
:param link: 链接
:return: 是否为http(s)://开头
"""
return link.startswith('http://') or link.startswith('https://')
@classmethod
def inner_link_replace_each(cls, path: str):
"""
内链域名特殊字符替换
:param path: 内链域名
:return: 替换后的内链域名
"""
old_values = ["http://", "https://", "www.", ".", ":"]
new_values = ["", "", "", "/", "/"]
for old, new in zip(old_values, new_values):
path = path.replace(old, new)
return path

Loading…
Cancel
Save