21-用户权限和动态菜单

419 阅读8分钟
回顾

接上篇,上次我们在前端项目中,补全了前端的在线HTTP测试,并且完善了一些功能。要知道,咱们这是一个接口测试平台。一个接口自动化平台,当然要有不同的用户权限和对应的菜单模块。因此这篇文章我们讲讲解用户权限和动态菜单的相关教程,和BUG。也在此发出求助,希望有人能针对文章最后的BUG,提出一些修改建议!感谢大家!

目标
  1. 后端新增用户权限相关
  2. 前端编写路由权限,实现动态菜单
  3. BUG描述

为何要有多个用户角色?

  1. 安全性和数据保护: 平台可能涉及到敏感信息和数据,不同的用户可能需要不同的权限级别来保护数据的安全性。只有授权的用户能够访问特定的数据和功能,从而防止未经授权的访问和数据泄露。
  2. 业务流程管理: 在一个企业内部的平台中,不同的部门和角色可能涉及到不同的业务流程和操作。例如,销售团队可能需要处理订单和客户信息,而财务团队可能需要访问财务数据和报表。根据不同的业务流程,用户权限需要进行相应的限制。
  3. 功能定制和个性化体验: 不同的用户可能对平台的需求和使用方式有所不同。有些用户可能只需要基本的功能,而有些用户可能需要更高级的功能。通过分配不同的权限,可以定制每个用户的体验,使其只能访问他们关心的功能。
  4. 组织结构和角色: 在一个组织中,不同的角色可能对平台有不同的需求。例如,管理层可能需要查看业务数据的汇总报表,而操作人员可能需要执行日常的数据输入和处理。通过设置不同的权限,可以适应不同的组织结构和角色需求。
  5. 遵循法规和政策: 某些行业和地区可能有特定的法规和政策要求,要求限制对某些数据和功能的访问。通过设置权限,可以确保平台的操作符合相关法规和政策要求。

动态菜单的好处?

  1. 安全性: 前端动态菜单可以根据用户的权限和角色来生成可见的菜单选项。这意味着用户只能看到他们被授权访问的页面和功能,从而提高了系统的安全性。未经授权的用户无法看到和访问不属于他们的菜单项,减少了数据泄露和恶意访问的风险。
  2. 用户体验: 前端动态菜单可以根据用户的角色和权限动态展示相关的功能选项,从而简化用户界面,减少不必要的复杂性。用户只会看到与其工作职责相关的菜单,提高了用户体验和工作效率。
  3. 可维护性: 前端动态菜单通常是基于角色和权限配置进行生成的,这使得系统的维护变得更加简单。当角色、权限或菜单项发生变化时,只需要更新配置,而无需修改大量的页面代码。
  4. 扩展性: 如果系统需要新增功能模块或页面,只需在菜单配置中添加相应的权限即可。新的功能将根据配置自动显示在菜单中,无需对现有代码进行大规模修改。
  5. 适应性: 前端动态菜单可以适应不同用户的需求和角色。不同的用户可以看到他们所需的不同菜单选项,从而实现个性化的用户界面。
  6. 统一性: 前端动态菜单可以统一管理和配置系统中的菜单项,确保菜单的一致性和统一性。这样可以避免不同开发人员在不同页面中创建不同的菜单,导致用户界面的混乱。
  7. 便捷性: 前端动态菜单可以根据用户的权限自动隐藏不可访问的功能选项,减少用户因权限问题而产生的困惑。用户只会看到他们有权限使用的功能,不会被无关的选项干扰。

如何设计?

  1. 权限控制: 不同角色的用户可能拥有不同的功能权限,例如管理员可以进行用户管理等操作,普通用户只能访问一般的功能页面。通过在菜单配置中设置不同的权限,可以确保只有具有相应权限的用户才能看到和访问相关菜单项。
  2. 界面简洁性: 不同角色的用户关注点和工作职责不同,他们不需要看到所有的菜单项。通过根据角色隐藏一些不相关的菜单,可以简化用户界面,减少不必要的干扰,提升用户体验。
  3. 系统安全性: 隐藏不相关的菜单可以减少未经授权用户的误操作和访问,从而提高系统的安全性。例如,某些敏感操作只应该由管理员进行,一般用户不应该看到相关菜单。
  4. 可维护性: 在权限配置的基础上,可以更灵活地管理不同角色的功能访问。当角色的权限需求变化时,只需调整相应的权限配置,而不需要修改页面代码。
  5. 扩展性: 如果未来需要新增一些功能模块,可以直接在菜单配置中添加相应的权限配置。这样新增功能会自动在合适的角色下显示,无需额外修改代码。

因此,综上所述,出于产品设计的出发点,我决定暂时拿掉前端Login页面中的注册功能,Login页面就只有登录功能,后期想要创建用户,必须要超级管理员在用户管理中新增,其他人无法自行注册。因此,我们还需要后端在初始化表的时候,初始化一个超级管理员admin

后端新增用户权限

  1. 权限相关字段role
  2. 初始化超级管理员admin

权限相关字段role

  1. User表中,新增role字段与默认参数role=0

image.png

  1. UserDto结构体中,新增role字段

image.png 至于其他部分,根据提示修修改改即可,不举例处理

初始化超级管理员admin

编辑src/app/dao/__init__.py文件

import asyncio
from datetime import datetime

from loguru import logger

from config import AdminConfig
from src.app.middleware.my_jwt import AbandonJWT
from src.app.models import Base, engine, async_session
from src.app.models.user import User
from sqlalchemy import or_, select, func  # 导入or_、select和func类/函数,用于构建SQL查询语句


async def init_user():
    """
    初始化注册用户
    """
    username = AdminConfig.USERNAME
    name = AdminConfig.NAME
    password = AdminConfig.PASSWORD
    role = 2
    email = AdminConfig.EMAIL

    try:
        # 采用aiomysql异步操作数据库
        async with async_session() as session:
            async with session.begin():
                # 检查用户名和邮箱是否已存在
                users = await session.execute(
                    select(User).where(or_(User.username == username, User.email == email)))
                counts = await session.execute(select(func.count(User.id)))
                if users.scalars().first():
                    logger.info("admin用户已存在")
                    return True
                # 注册时给密码加盐
                pwd = AbandonJWT.add_salt(password)
                user = User(username, name, pwd, email, role)
                user.last_login_at = datetime.now()
                session.add(user)
                await session.flush()
                session.expunge(user)
                return True
    except Exception as e:
        logger.error(f"初始化admin用户失败: {str(e)}")
        raise Exception(f"初始化admin用户失败: {e}")


Base.metadata.create_all(engine)


async def main():
    await init_user()


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

前端编写route限制

根据umi官网,我们可以熟悉:在umi中,搭配 access 官方文档一起使用,可以完成对路由权限的控制。因官方文档写的很明白,所以在此不多赘述,只是简单分成几步:

  1. 配置开启。同时需要 src/access.ts 提供权限配置。
  2. 约定了 src/access.ts 为我们的权限定义文件
  3. 扩展路由配置

新增src/access.ts文件

字面意思

约定权限定义文件

export default function access(initialState: { currentUser?: API.CurrentUser | undefined }) {
  const { currentUser } = initialState;
  return {
    // 判断用户是否为超级管理员
    isAdmin: currentUser && currentUser.usr_info.role === 2,
    // // 判断用户是否为组长
    // isLeader: currentUser && currentUser.usr_info.role === 1,
    // 判断用户是否为普通用户
    isUser: currentUser && currentUser.usr_info.role !== 2,
  };
}

扩展路由配置

image.png 在路由中,如图添加对应字段,即可限制路由权限。如果不加,默认是不需要权限。需要哪个权限,则添加对应字段即可。在此我们直接看最终效果:

超级管理员的菜单:

image.png

普通用户和组长的菜单:

image.png

至此,我们就实现了前后端根据不同用户权限展示不同菜单的功能,但是我在开头有说过,我们在写这部分功能的时候遇到了一个BUG,下面是我的BUG描述。

BUG描述

【简述】:首次登陆时,展示的菜单内容与实际不符,但是刷新页面后又正常展示。 【截图】:

image.png 【BUG定位】:我的login方法可以看到的我里面用的是异步请求。但是这里我遇到了问题:在我渲染页面的时候,我的返回还没有到,所以每次鉴权代码拿到的数据都是空,因此鉴权出来的没有匹配上isAdmin和isUser。导致最终展示出来的菜单,是没有限制任何权限的菜单。 【打印信息第二次定位】: 我们在鉴权代码中,加上打印信息,然后查看数据。

image.png

清除本地存储后的首次登陆打印:

image.png

刷新页面的第二次打印:

image.png 【解决方案】:暂时把这个BUG留给评论区的各位,大家可以提出自己的修改建议,一起成长!下一篇文章会将解决方案放出。

开源相关

组织地址

后端代码地址

前端代码地址

加群一起讨论相关问题呀!如果群二维码过期了,可以加我个人微信: yyi11yy 我拉你进群~

image.png

image.png