首页 > 编程语言 >java+springboot权限的设计(用户、角色、权限)和前端如何渲染用户所对应的权限菜单

java+springboot权限的设计(用户、角色、权限)和前端如何渲染用户所对应的权限菜单

时间:2024-09-03 16:55:17浏览次数:11  
标签:菜单 java 角色 request 用户 列表 权限 response

记得当时在学校的时候,觉得这个实现起来真的超级困难,想想就头大,毫无头绪,即便那时候去查资料看了很多大佬写的文章,看的时候感觉恍然大悟,直拍大腿,但是当我想要动手自己去做的时候,又不知道从哪开始切入,于是一直没有动手去做,直到最近在实习的时候,给了我这个任务,当我带着恐惧去自己动手实现这个功能的时候,当静下心来,发现是多么的有趣和清晰,那我们一起来看一下吧

一些专业术语网上都有,我在这就不多叙述,主打一个思路,把思路理明白了,一切就容易了
所谓的权限就是给用户赋予特定的角色(一个用户可以有很多角色,就像我们在现实中的身份<角色>可以有很多个),给角色赋予特定的权限(一个角色可以有很多权限,就像我们既可以吃喝玩乐又可以好好学习),这样用户就有了角色,角色有对应的权限,至此,用户就有了"权限"
在这里插入图片描述
这里的权限只举权限菜单的例子,主要理解思路
首先我们需要准备几张基础的数据表,你得有用户表,角色表,菜单表
注意:不用太纠结表单的字段,主要是思路!思路!

例如:
sys_user表

字段名称字段类型
idvarchar
user_namevarchar
pass_wordvarchar
statusvarchar
create_timedatetime
create_byvarchar
edite_timedatetime
edite_byvarchar
delete_flagchar

这里的delete_flag是用作删除标识,默认为0,这也是我进入实习之后才了解到的,不会真的去删除数据
然后就是,然后每个表都要有的字段就不再展示了:create_timecreate_byedite_timeedite_bydelete_flag

sys_role表(角色表)

字段名称字段类型
idvarchar
role_codevarchar
role_namevarchar
role_typevarchar
role_statusvarchar
remarkvarchar

sys_menu(菜单表)

字段名称字段类型
idvarchar
menu_codevarchar
menu_namevarchar
menu_pathvarchar
busi_statusvarchar
…………

这是基础的三张表,当然你还需要有菜单的子父级关系,这不是本篇文章的重点
好了我们需要了解怎么将用户、角色、权限菜单关联起来?
我们还需要两张表,一张用户角色表,一张角色权限表

用户角色表用来关联用户和角色
sys_right_user(用户角色表)

字段名称字段类型
idvarchar
role_idvarchar
role_codevarchar
user_idvarchar
busi_statusvarchar
remarkvarchar

怎么使用呢,首先我们需要创建角色信息
自己定义几个角色,我这随便起的名字
企业临时角色是因为我想实现新的企业注册的时候,给一个临时的角色,临时角色只可以查看自己的企业信息,然后通过提交信息,被审核之后才赋予对应的企业角色,这个不是必要的,根据自己的需求来
在这里插入图片描述
角色创建好了,你想先跟谁关联其实都是可以的,你可以先跟用户关联,也可以先跟权限菜单关联(这部分代码比较复杂
我提供一种思路
如果你要想跟用户关联的话,你可以在编辑用户的时候给它对应的权限,例如
在这里插入图片描述
下面我只展示部分代码(仅供参考),代码其实很多种方式

这个是编辑表单中的角色的select框

<el-form-item label="角色" prop="roleList">
	<el-select v-model="userInfo.roleList" multiple filterable allow-create default-first-option placeholder="请选择角色">
    	<el-option v-for="item in roleOptions"
                   :key="item.value"
                   :label="item.label"
                   :value="item.value">
        </el-option>
	</el-select>
</el-form-item>
    @ResponseBody
    @PostMapping("/editedUser")
    public JsonResult editedUser(@RequestBody UserVO userVO) {
        JsonResult jsonResult = JsonResult.getSuccessResult();
        try {
            userService.editedUser(userVO);
        } catch (Exception e) {
            logger.error("editedUser", e);
            jsonResult.setException(e);
        }
        return jsonResult;
    }

sysRightUser对应的是sys_right_user表中的字段
sysRole对应的是sys_role表中的字段

	@Transactional(rollbackFor = Exception.class)
    @Override
    public void editedUser(UserVO userVO) {
        Date now = DateUtil.getNow();
        List<SysRole> sysRoleList = userVO.getRoleList();
        List<SysRightUser> sysRightUsers = new ArrayList<>();
        if (sysRoleList != null && sysRoleList.size() > 0) {
            for (SysRole sysRole : sysRoleList) {
                SysRightUser sysRightUser = new SysRightUser();
                sysRightUser.setId(IdWorker.get32UUID());
                sysRightUser.setUserId(userVO.getId());
                sysRightUser.setRoleId(sysRole.getId());
                sysRightUser.setRoleCode(sysRole.getRoleCode());
                sysRightUser.setCreateTime(DateUtil.formatDate(now));
                sysRightUser.setCreateBy(Constant.CREATE_BY);
                sysRightUsers.add(sysRightUser);
            }
        }
        sysRightUserService.saveBatch(sysRightUsers);
    }

当然这个方法没有做全,还需要根据前端传递的角色列表判断和之前的角色列表的不同,现在有的 以前没有的 需要新增,现在有的 以前也有的 不需要操作,现在没有的 以前有的 需要做逻辑删除

那第二张表
sys_right_menu(角色权限表)

字段名称字段类型
idvarchar
role_idvarchar
role_codevarchar
menu_idvarchar
menu_codevarchar
busi_statusvarchar
remarkvarchar

那现在就来到了角色和权限菜单关联了,道理是一样的,在编辑角色中,编辑角色可以访问的权限菜单就可以了,例如:
在这里插入图片描述
这个是编辑角色中对应的权限菜单的form

<el-form-item label="权限菜单" prop="checkedMenus">
	<el-tree
		:data="menuList"
		:props="defaultProps"
		node-key="value"
		show-checkbox
		:check-on-click-node="true"
		:default-checked-keys="defaultCheckedKeys"
		:default-expanded-keys="defaultCheckedKeys"
		@check="handleCheck"
		ref="menuTree"
    >
	</el-tree>
</el-form-item>
defaultProps: {
                  children: 'children',
                  label: 'label'
               },

这个部分其实还是比较复杂的,你要在后端处理树结构,网上也有很多教程,大家可以自行搜索,本篇我们主要理清思路

sysRole对应的sys_role的字段
sysRightMenu对应的sys_right_menu中的字段
我下面的处理其实是很复杂的,大体看一下就可以

	@Transactional(rollbackFor = Exception.class)
    @Override
    public void updateRole(RoleVO roleVO) {
        // 获取当前时间
        Date now = DateUtil.getNow();
        // 创建SysRole对象
        SysRole sysRole = new SysRole();
        // 设置角色ID
        sysRole.setId(roleVO.getId());
        // 设置角色编码
        sysRole.setRoleCode(roleVO.getRoleCode());
        // 设置角色名称
        sysRole.setRoleName(roleVO.getRoleName());
        // 设置角色状态
        sysRole.setRoleStatus(roleVO.getRoleStatus());
        // 设置角色类型
        sysRole.setRoleType(roleVO.getRoleType());
        // 设置编辑人
        sysRole.setEditBy(Constant.CREATE_BY);
        // 设置编辑时间
        sysRole.setEditTime(DateUtil.formatDate(now));
        // 更新角色信息
        this.baseMapper.updateById(sysRole);

        // 获取角色对应的菜单列表
        List<CommonTree> commonTreeList = roleVO.getCommonTreeList();
        // 初始化要插入的菜单列表
        List<SysRightMenu> sysRightMenuList = new ArrayList<>();
        // 初始化要删除的菜单列表
        List<CommonTree> sysRightMenusToDelete = new ArrayList<>();
        if (commonTreeList != null && commonTreeList.size() > 0) {
            // 获取当前角色已关联的菜单ID集合
            Set<String> currentSysRightMenusIds = new HashSet<>();
            List<CommonTree> currentSysRightMenus = sysRightMenuService.listByRoleId(sysRole.getId());
            for (CommonTree currentSysRightMenu : currentSysRightMenus) {
                currentSysRightMenusIds.add(currentSysRightMenu.getValue());
            }
            // 遍历传入的菜单列表
                for (CommonTree tree : commonTreeList) {
                // 如果菜单已存在,则跳过
                if (currentSysRightMenusIds.contains(tree.getValue())) {
                    continue;
                }
                // 创建SysRightMenu对象
                SysRightMenu sysRightMenu = new SysRightMenu();
                // 设置菜单ID
                sysRightMenu.setId(IdWorker.get32UUID());
                // 设置角色ID
                sysRightMenu.setRoleId(sysRole.getId());
                // 设置角色编码
                sysRightMenu.setRoleCode(sysRole.getRoleCode());
                // 设置菜单ID
                sysRightMenu.setMenuId(tree.getValue());
                // 设置创建时间
                sysRightMenu.setCreateTime(DateUtil.formatDate(now));
                // 设置创建人
                sysRightMenu.setCreateBy(Constant.CREATE_BY);
                // 将菜单添加到要插入的列表
                sysRightMenuList.add(sysRightMenu);
            }
            // 遍历当前角色已关联的菜单列表
            for (CommonTree currentSysRightMenu : currentSysRightMenus) {
                // 如果菜单不在传入的菜单列表中,则添加到要删除的列表
                if (!commonTreeList.contains(currentSysRightMenu)) {
                    sysRightMenusToDelete.add(currentSysRightMenu);
                }
            }
        }
        // 批量插入菜单
        sysRightMenuService.saveBatch(sysRightMenuList);
        // 批量删除菜单
        sysRightMenuService.deleteRoleMenu(sysRightMenusToDelete, roleVO.getId());
    }

那么现在其实用户有了角色角色有了对应的权限菜单,那怎么根据用户渲染对应的菜单呢?
有很多方式,我这里说一下我的思路
假如现在用户有角色(新注册用户,注册逻辑里一般会给默认角色),登录的时候会经过filterLoginServlet
在loginServlet中进行登录验证(判断账号密码和验证码等逻辑),如果验证成功,我这里是用的redis存储的,就把用户的用户信息,角色,权限菜单存到redis中,然后登录成功之后会请求其他页面,这个时候会被filter拦截,判断是否登录进而判断权限菜单,最后将存到redis中的权限菜单列表返回给前端请求渲染列表的地方就完成了整个流程
在这里插入图片描述

下面是部分代码,可能写的不是很好,因为是手搓出来的,之前刚理清一点点思路写的哈哈,仅供参考仅供参考!

public class LoginServlet extends HttpServlet {

    private SysUserServiceImpl sysUserService;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取请求体中的数据
        StringBuilder jsonData = new StringBuilder();
        String line;
        try (BufferedReader reader = request.getReader()) {
            while ((line = reader.readLine()) != null) {
                jsonData.append(line);
            }
        }
        try {
            // 将请求体中的数据转换为JSON对象
            JSONObject jsonObject = new JSONObject(jsonData.toString());
            // 从JSON对象中获取用户名
            String username = jsonObject.getString("userName");
            // 从JSON对象中获取密码
            String password = jsonObject.getString("passWord");
            // 从JSON对象中获取验证码
            String captcha = jsonObject.getString("captcha");
            if(captcha != null && captcha.length() > 0){
                // 从session中获取验证码
                String sessionCaptcha = (String) request.getSession().getAttribute("captcha");
                // 比较session中的验证码和请求中的验证码是否一致
                if(!sessionCaptcha.equalsIgnoreCase(captcha)){
                    // 如果验证码错误,则返回状态码401,表示未授权
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    response.setContentType("application/json; charset=utf-8");
                    response.getWriter().write("{\"success\": false, \"message\": \"验证码错误!\"}");
                    return;
                }
            }
            // 创建UserParam对象,并设置用户名和密码
            UserParam userParam = new UserParam();
            userParam.setUserName(username);
            userParam.setPassWord(password);

            // 进行登录验证
            Boolean login = sysUserService.login(userParam);
            if (login){
                // 进行登录成功后的处理
                sysUserService.loginSuccess(request,response,userParam);
                response.setStatus(HttpServletResponse.SC_OK);
                response.setHeader("Content-Type", "application/json");
                response.getWriter().write("{\"success\": true, \"message\": \"登录成功!\"}");
            }else {
                // 登录失败,返回状态码401,表示未授权
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.setContentType("application/json; charset=utf-8");
                response.getWriter().write("{\"success\": false, \"message\": \"用户名或密码错误!\"}");
            }
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    public void init() throws ServletException {
        // 获取WebApplicationContext对象
        WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        // 从Spring容器中获取SysUserServiceImpl类型的Bean对象,并赋值给sysUserService变量
        sysUserService = springContext.getBean(SysUserServiceImpl.class);
    }
}

这个是上面出现的登录成功之后处理逻辑loginSuccess方法,当然下面的方法如果角色和角色之间有重复的菜单也会被添加进去,我是在前端取的时候做的处理,其实我写的还是比较复杂的,肯定有更好的方法,大家仅供参考

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Map<String, Object> loginSuccess(HttpServletRequest request, HttpServletResponse response,UserParam userParam) {
        // 创建一个存储数据的Map
        Map<String, Object> data = new HashMap<>();
        // 调用sysUserMapper的userLogin方法,根据用户参数查询用户信息,并将结果赋值给userVO
        UserVO userVO = sysUserMapper.userLogin(userParam);
        // 调用sysRightUserMapper的queryRightUserByUserId方法,根据用户ID查询用户角色列表,并将结果赋值给roleVOS
        ArrayList<RoleVO> roleVOS = sysRightUserMapper.queryRightUserByUserId(userVO.getId());
        // 创建一个空的菜单列表
        List<CommonTree> menuList = new ArrayList<>();
        // 如果角色列表不为空且大小大于0
        if(roleVOS != null && roleVOS.size() > 0){
            // 遍历角色列表
            for (RoleVO roleVO : roleVOS) {
                // 调用sysRightMenuMapper的listByRoleId方法,根据角色ID查询菜单列表,并将结果赋值给menus
                List<CommonTree> menus = sysRightMenuMapper.listByRoleId(roleVO.getRoleId());
                // 遍历菜单列表
                for (CommonTree menu : menus) {
                    // 如果菜单列表中没有包含当前菜单
                    if (!menuList.contains(menu)) {
                        // 将当前菜单添加到菜单列表中
                        menuList.add(menu);
                    }
                }
            }
        }

        // 生成一个随机的ticketId
        String ticketId = UUID.randomUUID().toString();
        // 拼接ticket的键
        String ticket = CacheUtil.PREFIX_TICKET_USER + ticketId;
        // 将ticketId存入data中
        data.put("ticketId", ticketId);
        // 将用户名存入data中
        data.put("username", userVO.getUserName());
        // 将用户ID存入data中
        data.put("userId", userVO.getId());
        // 将角色列表存入data中
        data.put("roleList", roleVOS);
        // 将菜单列表存入data中
        data.put("menuList", menuList);
        // 将data存入redis中,并设置过期时间为1200秒
        redisUtil.set(ticket,data,1200);
        // 将ticket存入session中
        request.getSession().setAttribute("ticket", ticket);
        // 将用户ID存入session中
        request.getSession().setAttribute("userID", userVO.getId());
        // 返回data
        return data;
    }

这样一步用户的信息就存进去了,登录之后肯定紧接着会请求到首页,紧接着会被filter拦截

@Component
public class LoginFilter implements Filter {
    @Autowired
    private RedisUtil redisUtil;
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        String requestURL = request.getRequestURI();

        // 过滤不需要处理的请求URL
        //根据需要
        if(requestURL.contains("/login") || requestURL.contains("/register") || requestURL.contains("/captcha")) {
            filterChain.doFilter(request, response);
            return;
        }

        // 检查用户是否已登录
        // 用户未登录,跳转到登录页面
        try {
            if (!isUserLoggedIn(request)) {
                response.sendRedirect("/index.html");
                return;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 用户已登录,放行
        filterChain.doFilter(request, response);
    }
    private boolean isUserLoggedIn(HttpServletRequest request) throws Exception {
        // 从Session中获取Ticket
        // 刚刚在loginSuccess中存的
        String ticket = (String) request.getSession().getAttribute("ticket");

        if (ticket != null && !ticket.isEmpty()) {
            // 判断Redis中是否存在Ticket
            boolean hasKey = redisUtil.hasKey(ticket);
            if (hasKey) {
                // 从Redis中获取用户信息
                Map<String,Object> userMap = (Map<String, Object>) redisUtil.get(ticket);
                // 获取用户角色列表
                ArrayList<RoleVO> roleList = (ArrayList<RoleVO>) userMap.get("roleList");
                // 获取用户菜单列表
                ArrayList<CommonTree> menuList = (ArrayList<CommonTree>) userMap.get("menuList");
                // 构建树形菜单列表
                List<CommonTree> commonTrees = CommonTreeUtil.buildListTree(menuList, "-1");
                // 将角色列表和菜单列表设置到request属性中
                request.setAttribute("roleList", roleList);
                request.setAttribute("commonTrees", commonTrees);
            }
            return hasKey;
        }
        return false;
    }
}

这样权限列表已经存到request中了,我们只需要在前端请求的时候,返回给他就行了
我前段初始化会调用这个接口 /getCallingTreeByUser

    @PostMapping("/getCallingTreeByUser")
    public JsonResult<CommonTree> getCallingTreeByUser(HttpServletRequest request) {
    	//获取到requst中存储的权限菜单列表并进行处理成树结构
        List<CommonTree> menuList = (List<CommonTree>) request.getAttribute("commonTrees");
        JsonResult<CommonTree> jsonResult = JsonResult.getSuccessResult();
        jsonResult.setData(menuList);
        return jsonResult;
    }

好了,到这基本上就结束了,有没有明白一些呢,我也是最近刚刚理清一点点,所有肯定有很多不是很规范,不严谨的地方,也有很多我还没有涉及到的方面,还请大佬们多多指导!

标签:菜单,java,角色,request,用户,列表,权限,response
From: https://blog.csdn.net/skarv/article/details/141851503

相关文章

  • javascript变量
    定义变量var声明变量的关键字vara;vara=10;varb=20.8;varc="demo";定义时不区分数据类型,但是使用时存在类型的区分变量类型:①基本类型:(零零散散不可拆分)数字类型1010.6字符串"aa"'aaa'布尔类型真/假true/falseundefined类型即声明变量但不进行赋......
  • Java基础 韩顺平老师的 泛型 的部分笔记
    553,泛型引入packagecom.hspedu.list_;importjava.util.*;importjava.util.concurrent.CompletionService;@SuppressWarnings({"all"})publicclassMap_{publicstaticvoidmain(String[]args){//使用传统的方法来解决ArrayListarrayLis......
  • Java微服务架构设计:构建可扩展的服务
    Java微服务架构设计:构建可扩展的服务大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!微服务架构是一种将应用程序作为一套小服务开发的方法,每个服务运行在其独立的进程中,并通过轻量级的通信机制(通常是HTTPRESTfulAPI)进行交互。在Java中,构建微服务通......
  • Java中的依赖注入:Spring框架核心概念解析
    Java中的依赖注入:Spring框架核心概念解析大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java企业级应用开发中,Spring框架已成为事实上的标准。Spring的核心之一是依赖注入(DependencyInjection,DI),它是一种实现控制反转(InversionofControl,IoC)......
  • idea java开发 如何引入 一个 第三方 文件夹SDK源码,是SDK源码 不是 jar包
    在IntelliJIDEA中引入第三方文件夹作为SDK源码,而不是以jar包的形式,可以通过以下步骤来实现:打开项目:启动IntelliJIDEA,并打开你的项目。打开项目结构设置:点击菜单栏中的File->ProjectStructure...或使用快捷键Ctrl+Alt+Shift+S打开项目结构设置窗口。添加......
  • Java服务端数据库连接:连接池的资源优化
    Java服务端数据库连接:连接池的资源优化大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java服务端开发中,数据库连接池是提高数据库操作效率的关键技术之一。然而,随着系统负载的增加,如何优化连接池的资源使用成为了一个重要的问题。本文将探讨如何通......
  • Java服务端数据库连接:连接池的故障恢复策略
    Java服务端数据库连接:连接池的故障恢复策略大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java服务端应用中,数据库连接池是核心组件之一,它管理着数据库连接的生命周期。然而,数据库连接可能会因为多种原因(如网络问题、数据库服务重启等)而出现故障。......
  • 深入理解Java内存模型:对并发编程的启示
    深入理解Java内存模型:对并发编程的启示大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java并发编程中,Java内存模型(JMM)是一个至关重要的概念。它定义了Java程序中各种变量的访问规则,以及这些变量如何与计算机内存交互。正确理解JMM对于编写高效、可......
  • 深入解读JMC:轻松获取Java应用的性能数据
    对于我们常用的HotSpot来说,有更强大的工具,那就是JMC。JMC集成了一个非常好用的功能:JFR(JavaFlightRecorder)。FlightRecorder源自飞机的黑盒子,是用来录制信息然后事后分析的。在Java11中,它可以通过jcmd命令进行录制,主要包括configure、check、start、dump、stop......
  • JMC的秘密武器:如何获取并分析Java性能数据
    对于我们常用的HotSpot来说,有更强大的工具,那就是JMC。JMC集成了一个非常好用的功能:JFR(JavaFlightRecorder)。FlightRecorder源自飞机的黑盒子,是用来录制信息然后事后分析的。在Java11中,它可以通过jcmd命令进行录制,主要包括configure、check、start、dump、stop......