Browse Source

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

master
insistence 7 months ago
parent
commit
43f903c6e0
  1. 30
      ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py
  2. 227
      ruoyi-fastapi-backend/module_admin/service/login_service.py

30
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):
@ -53,3 +54,30 @@ class SmsCode(BaseModel):
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

227
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':
router_list_data['name'] = permission.path.capitalize()
router_list_data['hidden'] = False if permission.visible == '0' else True
if permission.parent_id == 0:
router_list_data['component'] = 'Layout'
router_list_data['path'] = f'/{permission.path}'
else:
router_list_data['component'] = 'ParentView'
router_list_data['path'] = permission.path
if permission.is_frame == 1:
router_list_data['redirect'] = 'noRedirect'
else:
router_list_data['path'] = permission.path
if children: if children:
router_list_data['alwaysShow'] = True menu_list_data.children = children
router_list_data['children'] = children menu_list.append(menu_list_data)
router_list_data['meta'] = {
'title': permission.menu_name, return menu_list
'icon': permission.icon,
'noCache': False if permission.is_cache == '0' else True, @classmethod
'link': permission.path if permission.is_frame == 0 else None def __generate_user_router_menu(cls, permission_list: List[MenuTreeModel]):
} """
elif permission.menu_type == 'C': 工具方法根据菜单树信息生成路由信息树形嵌套数据
router_list_data['name'] = permission.path.capitalize() :param permission_list: 菜单树列表信息
router_list_data['path'] = permission.path :return: 路由信息树形嵌套数据
router_list_data['query'] = permission.query """
router_list_data['hidden'] = False if permission.visible == '0' else True router_list: List[RouterModel] = []
router_list_data['component'] = permission.component for permission in permission_list:
router_list_data['meta'] = { router = RouterModel(
'title': permission.menu_name, hidden=True if permission.visible == '1' else False,
'icon': permission.icon, name=RouterUtil.get_router_name(permission),
'noCache': False if permission.is_cache == '0' else True, path=RouterUtil.get_router_path(permission),
'link': permission.path if permission.is_frame == 0 else None component=RouterUtil.get_component(permission),
} query=permission.query,
router_list.append(router_list_data) 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
)
)
c_menus = permission.children
if c_menus and permission.menu_type == 'M':
router.always_show = True
router.redirect = 'noRedirect'
router.children = cls.__generate_user_router_menu(c_menus)
elif RouterUtil.is_menu_frame(permission):
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