记得其时在学校的时候,觉得这个实现起来真的超级困难,想想就头大,毫无头绪,即便当时候去查资料看了很多大佬写的文章,看的时候感觉名顿开,直拍大腿,但是当我想要动手自己去做的时候,又不知道从哪开始切入,于是一直没有动手去做,直到最近在实习的时候,给了我这个任务,当我带着恐惊去自己动手实现这个功能的时候,当静下心来,发现是多么的有趣和清晰,那我们一起来看一下吧
一些专业术语网上都有,我在这就不多叙述,主打一个思路,把思路理明白了,一切就容易了
所谓的权限就是给用户赋予特定的脚色(一个用户可以有很多脚色,就像我们在现实中的身份<脚色>可以有很多个),给脚色赋予特定的权限(一个脚色可以有很多权限,就像我们既可以吃喝玩乐又可以好好学习),这样用户就有了脚色,脚色有对应的权限,至此,用户就有了"权限"
这里的权限只举权限菜单的例子,主要理解思路
首先我们必要预备几张底子的数据表,你得有用户表,脚色表,菜单表
留意:不用太纠结表单的字段,主要是思路!思路!
例如:
sys_user表
字段名称字段类型idvarcharuser_namevarcharpass_wordvarcharstatusvarcharcreate_timedatetimecreate_byvarcharedite_timedatetimeedite_byvarchardelete_flagchar 这里的delete_flag是用作删除标识,默认为0,这也是我进入实习之后才了解到的,不会真的去删除数据
然后就是,然后每个表都要有的字段就不再展示了:create_time,create_by,edite_time,edite_by,delete_flag
sys_role表(脚色表)
字段名称字段类型idvarcharrole_codevarcharrole_namevarcharrole_typevarcharrole_statusvarcharremarkvarchar sys_menu(菜单表)
字段名称字段类型idvarcharmenu_codevarcharmenu_namevarcharmenu_pathvarcharbusi_statusvarchar………… 这是底子的三张表,当然你还必要有菜单的子父级关系,这不是本篇文章的重点
好了我们必要了解怎么将用户、脚色、权限菜单关联起来?
我们还必要两张表,一张用户脚色表,一张脚色权限表
用户脚色表用来关联用户和脚色
sys_right_user(用户脚色表)
字段名称字段类型idvarcharrole_idvarcharrole_codevarcharuser_idvarcharbusi_statusvarcharremarkvarchar 怎么利用呢,首先我们必要创建脚色信息
自己界说几个脚色,我这随便起的名字
企业临时脚色是因为我想实现新的企业注册的时候,给一个临时的脚色,临时脚色只可以检察自己的企业信息,然后通过提交信息,被考核之后才赋予对应的企业脚色,这个不是须要的,根据自己的需求来
脚色创建好了,你想先跟谁关联实在都是可以的,你可以先跟用户关联,也可以先跟权限菜单关联(这部分代码比较复杂)
我提供一种思路
如果你要想跟用户关联的话,你可以在编辑用户的时候给它对应的权限,例如
下面我只展示部分代码(仅供参考),代码实在很多种方式
这个是编辑表单中的脚色的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(脚色权限表)
字段名称字段类型idvarcharrole_idvarcharrole_codevarcharmenu_idvarcharmenu_codevarcharbusi_statusvarcharremarkvarchar 那如今就来到了脚色和权限菜单关联了,原理是一样的,在编辑脚色中,编辑脚色可以访问的权限菜单就可以了,例如:
这个是编辑脚色中对应的权限菜单的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());
- }
复制代码 那么如今实在用户有了脚色,脚色有了对应的权限菜单,那怎么根据用户渲染对应的菜单呢?
有很多方式,我这里说一下我的思路
假如如今用户有脚色(新注册用户,注册逻辑里一般会给默认脚色),登录的时候会经过filter和LoginServlet
在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;
- }
复制代码 好了,到这基本上就结束了,有没有明白一些呢,我也是最近刚刚理清一点点,全部肯定有很多不是很规范,不严谨的地方,也有很多我还没有涉及到的方面,还请大佬们多多引导!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |