Java:72-项目的权限管理模块

项目的权限管理模块

在上一个博客中(71章博客),我们完成了广告管理模块和用户管理模块,接下来我们完成最后的权限管理模块
权限管理模块 :
权限概念介绍:
权限:权利(能做的)和限制(不能做的),在权限范围内做好自己的事情,不该看的不看,不该做的不做
认证: 验证用户名密码是否正确的过程
授权: 对用户所能访问的资源进行控制(动态显示菜单、url级别的权限控制)
为什么要实现权限系统 :
首先系统需要进行登陆才能访问
其次不同登陆用户要有不同的权利
而且要有不同的菜单(例如财务经理针对系统中财务相关模块进行操作,人事经理针对系统中人事模块进行操作)
权限控制基本原理 :
ACL(Access Control Lists,缩写ACL):
ACL是最早也是最基本的一种访问控制机制
它的原理非常简单:每一项资源,都配有一个列表,这个
列表记录的就是哪些用户可以对这项资源执行CRUD中的那些操作。当系统试图访问这项资源时,会首
先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作
总得来说,ACL是一种面向资源的访问控制模型,它的机制是围绕"资源"展开的
基于角色的访问控制RBAC(Role-Based Access Control):
RBAC是把用户按角色进行归类,通过用户的角色来确定用户能否针对某项资源进行某项操作
RBAC相对于ACL最大的优势就是它简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
而用户与权限变成了间接关联
RBAC模型使得访问控制,特别是对用户的授权管理变得非常简单和易于维护,因此有广泛的应用

在这里插入图片描述

可以发现ACL在进行判断是否有对应权限时,会查询多条数据(多个对应)
而若使用RBAC,只需要查一条数据即可,执行速度变快,这样就简化了查询(虽然硬盘空间多了,但是可以忽略不计)
实际上就是将一个表进行数据关联,而我们与这个表进行关联
规则一:每个登陆的用户,可以有多个角色
规则二:每个角色又可以拥有多个权限(包含菜单和资源)
权限管理模块功能分析:
权限模块主要细分为角色模块、菜单模块、资源模块
将针对细分的三个模块进行具体功能实现
权限管理模块:
实现以下功能:
管理模块里面的可以直接叫做模块
角色列表查询和条件查询一起操作(角色模块),但凡有条件的,基本也是可以进行查询所有的
所有到达页面可以进行不传参数,使得查询所有,也就能使得一个接口,都可以操作了
如前面的课程管理模块的多条件查询和用户的分页和条件查询和广告的分页查询 ,也没有对应的查询所有
而广告位由于没有对应的条件或者分页查询使得顺便查询所有,所以就只能操作查询所有了
新增角色(角色模块)
回显角色(角色模块)
修改角色(角色模块)
分配菜单(包含查询菜单以及对应角色菜单的显示和保存分配的菜单,角色模块)
删除角色(角色模块)
菜单列表查询(菜单模块)
回显菜单(菜单模块)
包含对应父菜单回显和全部菜单信息回显,即添加和修改都需要回显,他们共用父菜单回显,修改操作单独使用全部菜单信息回显
新增菜单(菜单模块)
修改菜单(菜单模块)
删除菜单(菜单模块)
资源分类,分页及其多条件查询(资源模块)
下面三个就是与前面用户管理模块联系的:
用户登陆(用户管理模块)
动态菜单展示(权限管理模块,也可以说成用户管理模块)
用户关联角色(用户管理模块)
对应数据库表已经在71章博客里面说明了
表关系介绍 :
ER图 :

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

权限管理模块的角色模块实现 :
角色列表查询
需求分析
需求:点击角色列表按钮进行角色列表展示

在这里插入图片描述

Dao层:RoleMapper :
package com.lagou.dao;

import com.lagou.domain.Role;

import java.util.List;

/**
 *
 */
public interface RoleMapper {

    /*
    角色列表条件查询
     */
    public List<Role> findAllRole(Role role);

}

对应映射配置(RoleMapper.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lagou.dao.RoleMapper">

    <select id="findAllRole" parameterType="Role" resultType="Role">
        SELECT * FROM roles
        <where>
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
        </where>
    </select>

</mapper>
Service层:RoleService及其实现类 :
package com.lagou.service;

import com.lagou.domain.Role;

import java.util.List;

/**
 *
 */
public interface RoleService {

    /*
    角色列表条件查询
    */
    public List<Role> findAllRole(Role role);
    //为了好扩展,使用类
}

package com.lagou.service.impl;

import com.lagou.dao.RoleMapper;
import com.lagou.domain.Role;
import com.lagou.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 */
@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    private RoleMapper roleMapper;
    /*
   角色列表查询
    */
    public List<Role> findAllRole(Role role){

        List<Role> allRole = roleMapper.findAllRole(role);
        return allRole;


    }
}

Web层:RoleController :
package com.lagou.controller;

import com.lagou.domain.ResponseResult;
import com.lagou.domain.Role;
import com.lagou.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 *
 */
@RestController
@RequestMapping("/role")
public class RoleController {

    @Autowired
    private RoleService roleService;

    //角色列表条件查询
    @RequestMapping("/findAllRole")
    public ResponseResult findAllRole(@RequestBody Role role) {
        List<Role> allRole = roleService.findAllRole(role);
        ResponseResult responseResult = new ResponseResult(true, 200, "查询角色成功", allRole);
        return responseResult;
    }
}

使用Postman测试接口
接下来我们操作新增角色
Dao层:添加部分RoleMapper :
/*
    添加角色
     */
    public void saveRole(Role role);
添加部分RoleMapper.xml:
 <!--添加角色-->
    <insert id="saveRole" parameterType="Role">
        insert into roles
        values (null, #{code}, #{name}, #{description}, #{createdTime}, #{updatedTime}, #{createdBy}, 
        #{updatedBy})
    </insert>
Service层:添加部分RoleService及其实现类 :
    /*
    添加角色
     */
    public void saveRole(Role role);
 /*
   添加角色
    */
    @Override
    public void saveRole(Role role) {
        Date date = new Date();
        role.setCreatedTime(date);
        role.setUpdatedTime(date);
        role.setCreatedBy("system");
        role.setUpdatedBy("system");
        roleMapper.saveRole(role);
    }
Web层:添加部分RoleController :
/*
    添加角色,后面会进行修改角色扩展
     */
    @RequestMapping("/saveOrUpdateRole")
    public ResponseResult saveOrUpdateRole(@RequestBody Role role) {
        roleService.saveRole(role);

        ResponseResult responseResult = new ResponseResult(true, 200, "添加角色成功", null);
        return responseResult;
    }
使用Postman测试接口
在这里说明一下Postman对应的表单类型

在这里插入图片描述

其中对于表单来说
form-data:相当于multipart/form-data,传递二进制或者键值对,具体获取数据可以查看56章博客的流程
x-www-form-urlencoded:相当于application/x-www-form-urlencoded(这是表单默认的形式),只能传递键值对
但后台根据这些数据有不同的读取方式,所以有些方法只能操作对应类型,如getParameter()方法
可以得到x-www-form-urlencoded对应的键值对数据,而得不到form-data对应的键值对数据,即一般都是返回null值
raw:通常用来操作json数据,即传递字符串数据
binary:相当于相当于Content-Type:application/octet-stream,只能操作二进制数据,没有键值对(即不能操作键值对)
在修改角色之前,我们需要回显:
Dao层:添加部分RoleMapper :
/*
    回显角色信息(根据id查询角色信息)
     */
    public Role findRoleById(Integer id);
添加部分RoleMapper.xml:
 <!--根据id查询角色信息-->
    <select id="findRoleById" parameterType="int" resultType="Role">
        SELECT * FROM roles
        where id = #{id}
    </select>
Service层:添加部分RoleService及其实现类 :
 /*
    回显角色信息(根据id查询角色信息)
     */
    public Role findRoleById(Integer id);
//根据id查询角色信息
    @Override
    public Role findRoleById(Integer id) {
        Role roleById = roleMapper.findRoleById(id);

        return roleById;
    }
Web层:添加部分RoleController :
  /*
    根据id查询角色信息
     */
    @RequestMapping("/findRoleById")
    public ResponseResult findRoleById(Integer id) {
        Role roleById = roleService.findRoleById(id);
        ResponseResult responseResult = new ResponseResult(true, 200, "根据id查询角色成功", roleById);
        return responseResult;
    }
使用Postman测试接口
回显操作完毕后,我们可以进行修改操作了
Dao层:添加部分RoleMapper :
  /*
    修改角色
     */
    public void updateRole(Role role);
添加部分RoleMapper.xml:
 <!--修改角色-->
    <update id="updateRole" parameterType="Role">
        update roles
        <trim prefix="set" suffixOverrides=",">

            <if test="code != null and code != ''">
                code = #{code},
            </if>
            <if test="name != null and name != ''">
                name = #{name},
            </if>
            <if test="description != null and description != ''">
                description = #{description},
            </if>
            <if test="createdTime != null">
                created_time = #{createdTime},
            </if>
            <if test="updatedTime != null">
                updated_time = #{updatedTime},
            </if>
            <if test="createdBy != null and createdBy != ''">
                created_by = #{createdBy},
            </if>
            <if test="updatedBy != null and updatedBy != ''">
                updated_by = #{updatedBy},
            </if>

        </trim>
        <where>
            <if test="id != null">
                and id = #{id}
            </if>
        </where>
    </update>
Service层:添加部分RoleService及其实现类 :
 /*
  修改角色
   */
    public void updateRole(Role role);
    /*
修改角色
 */
    @Override
    public void updateRole(Role role) {
        role.setUpdatedTime(new Date());
        roleMapper.updateRole(role);
    }
Web层:修改部分RoleController(扩展saveOrUpdateRole方法):
 @RequestMapping("/saveOrUpdateRole")
    public ResponseResult saveOrUpdateRole(@RequestBody Role role) {
        ResponseResult responseResult;
        if(role.getId() == null) {

            roleService.saveRole(role);
            responseResult = new ResponseResult(true, 200, "添加角色成功", null);

        }else{

            roleService.updateRole(role);
            responseResult = new ResponseResult(true, 200, "修改角色成功", null);

        }
        return responseResult;
    }
使用Postman测试接口
分配菜单
需求分析
需求:点击分配菜单,回显可选择菜单信息,并回显选中状态

在这里插入图片描述

具体分析:

在这里插入图片描述

首先先得到所有的菜单节点信息,然后再根据角色id查询关联的菜单节点信息
这两个操作,统称为菜单的回显,最后再进行操作保存
那么我们就首先操作菜单的回显
代码编写思路:
对应图解:

在这里插入图片描述

在这里插入图片描述

在回显之前,我们先分析一下对应menu表(这是对应菜单表)

在这里插入图片描述

在这里插入图片描述

id是菜单对应的id,parent_id表示对应父菜单是哪个id
根据表来看,parent_id等于-1的基本都是父菜单,相当与基本没有父菜单了,所有基本就是最父级(顶级)的菜单
其余的都是子菜单
我们发现,这个表中,对应顶级菜单(父级菜单)和子菜单是在同一个表中,而由于前端需要有对应的父子关系
那么我们直接的select * from menu语句是很难满足前端要形成父子关系的需求的
因为查询的结果都是同级,也就是在同级的List中(不嵌套的关系)
那么前端必须通过逻辑来进行父子联系(如对应parent_id与id进行判断是否相等来实现),使得出现层级关系
但是我们可以通过语句来直接实现这样的关系,降低了对应代码量的操作
对应sql语句操作如下:
-- 这里我们使用自连接查询,也就是一张表拆分两张表来看
-- 看如下逐步操作,一步一步的实现自连接查询
-- 首先先查询顶级(父级)菜单(根据上面描述,所有顶级菜单的parent_id的值都为-1)
select * from menu where parent_id = -1
-- 然后再查询的顶级菜单所关联的子级菜单信息
select * from menu where parent_id = 1

-- 发现我们查询了所有父级菜单信息,可以看成一个表
-- 也查询了对应父级菜单的子菜单信息,也可以看成一个表
-- 那么我们就可以使用mybatis的嵌套查询,来实现对应操作
-- 而对于两张表有联系的表来说,我们通常需要在对应主键类中添加对应外键集合字段变量,即会使用嵌套查询
-- 虽然这里实际上是一个表,但是却实现了需要两个表的联系,所有我们在对应的Menu类中也添加对应字段,如下
对应的添加部分Menu类:
//声明一个集合
    //代表当前父级菜单所关联的子级菜单
    private List<Menu> subMenuList;
	//自连接的操作,都是操作同一个表,所以对应类型(泛型)一般都是本类

    public List<Menu> getSubMenuList() {
        return subMenuList;
    }

    public void setSubMenuList(List<Menu> subMenuList) {
        this.subMenuList = subMenuList;
    }
好了现在开始菜单的回显:
先进行菜单节点的查询
Dao层:MenuMapper:
package com.lagou.dao;

import com.lagou.domain.Menu;

import java.util.List;

/**
 *
 */
public interface MenuMapper {

    /*
    查询所有父子菜单信息
     */
    public List<Menu> findSubMenuListByPid(Integer pid);
}

对应映射配置(MenuMapper.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lagou.dao.MenuMapper">

    <resultMap id="menuResult" type="Menu">
        <id column="id" property="id"></id>
        <result column="href" property="href"></result>
        <result column="icon" property="icon"></result>
        <result column="name" property="name"></result>
        <result column="parent_id" property="parentId"></result>
        <result column="description" property="description"></result>
        <result column="orderNum" property="order_num"></result>
        <result column="shown" property="shown"></result>
        <result column="created_time" property="createdTime"></result>
        <result column="updated_time" property="updatedTime"></result>
        <result column="created_by" property="createdBy"></result>
        <result column="updated_by" property="updatedBy"></result>
        <!--
        select若要调用其他配置文件,则需要对应的全路径加上对应标签id,而这里调用自身的对应标签id
        那么就不需要对应全路径了,可以直接写上对应标签id,使得调用对应sql语句的执行
        这是之所以使用findSubMenuListByPid,是因为对应sql语句是一样的
        -->
        <collection property="subMenuList" ofType="Menu" select="findSubMenuListByPid" column="id">
            <!--这个id使得下面的参数是需要变化的,也就是说不能直接写-1了-->
        </collection>
    </resultMap>

    <!--查询所有父子菜单信息-->
    <select id="findSubMenuListByPid" parameterType="int" resultMap="menuResult">
        select *
        from menu
        where parent_id = #{id} order by order_num 
        <!--这里将排序字段进行排序一下-->
    </select>

    <!--
    从上面我们可以发现,当一个表自己进行联系时,通常都是跟某个字段进行联系,该字段我称之为内部外键
    而这个字段就是导致进行嵌套查询的主要操作,而其他的表的外键,则可以称之为外部外键
    我们发现无论是判断还是查询,都是这个内部外键进行主要作用
    这是因为他既可以得到对应不同的菜单,也可以得到对应菜单中有哪些菜单
    即在创建表的时候,就赋予了他数据的作用了,一部分当成主要数据(顶部菜单)
    一部分与主键联系,当成对应的子菜单
    所有说,内部外键在创建的时候,基本就会需要嵌套查询了,且对应语句一样,这是数据的作用
    -->
</mapper>
Service层:MenuService及其实现类 :
package com.lagou.service;

import com.lagou.domain.Menu;

import java.util.List;

/**
 *
 */
public interface MenuService {

    /*
  查询所有父子菜单信息
   */
    public List<Menu> findSubMenuListByPid(Integer pid);
}

package com.lagou.service.impl;

import com.lagou.dao.MenuMapper;
import com.lagou.domain.Menu;
import com.lagou.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 */
@Service
public class MenuServiceImpl implements MenuService {

    @Autowired
    private MenuMapper menuMapper;

    /*
 查询所有父子菜单信息
  */
    @Override
    public List<Menu> findSubMenuListByPid(Integer pid) {
        List<Menu> subMenuListByPid = menuMapper.findSubMenuListByPid(pid);

        return subMenuListByPid;
    }
}

Web层:MenuController :
package com.lagou.controller;

import com.lagou.domain.Menu;
import com.lagou.domain.ResponseResult;
import com.lagou.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;

/**
 *
 */
@RestController
@RequestMapping("/menu")
public class MenuController {

    @Autowired
    private MenuService menuService;

      /*
  查询所有父子菜单信息
   */
    //写在角色模块的Controller里面也可以,因为这个操作也是属于角色模块的
      // 但这里我们根据主要表来决定对应的层级(如dao,service,controller)
    // 但最好还是根据接口文档来编写
    //这里就不根据接口文档(接口文档是写在角色模块的Controller里面)编写了(最好不要这样)
    @RequestMapping("/findAllMenu")
    public ResponseResult findSubMenuListByPid() {
        //手动的设置查询父菜单,且基本是必须设置-1的,因为表就是这样,当然,你也可以进行参数的传递
        //但这个-1是基本不会变化的,所以也就没必要了
        //所以可以直接写-1,而不需要传递参数
        List<Menu> subMenuListByPid = menuService.findSubMenuListByPid(-1); //-1与父菜单关联

        HashMap<String, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("parentMenuList", subMenuListByPid);
        ResponseResult responseResult = new ResponseResult(true, 200, "查询所有的父子菜单信息成功", 
                                                           objectObjectHashMap);
        return responseResult;
    }
}

使用Postman测试接口
当我们进行查询所有的父子菜单信息后,那么就需要对应角色id关联的菜单显示了
需求分析:

在这里插入图片描述

根据角色ID查询关联菜单ID
Dao层:添加部分MenuMapper:
/*
    根据角色id查询该角色的菜单信息id
     */

    public List<Integer> findMenuByRoleId(Integer roleid);
添加部分MenuMapper.xml:
 <!-- 
根据角色id查询该角色的菜单信息id
注意:是id,不是parent_id,所以会有对应角色id的全部菜单显示(包括父子菜单)
-->

    <select id="findMenuByRoleId" parameterType="int" resultType="int">
        SELECT m.id FROM roles r INNER JOIN role_menu_relation rl ON r.id = rl.role_id
                                 INNER JOIN menu m ON m.id = rl.menu_id WHERE r.id = #{id}
    </select>
Service层:添加部分MenuService及其实现类 :
/*
    根据角色id查询该角色的菜单信息id
     */

    public List<Integer> findMenuByRoleId(Integer roleid);
 /*
  根据角色id查询该角色的菜单信息id
   */
    @Override
    public List<Integer> findMenuByRoleId(Integer roleid) {
        List<Integer> menuByRoleId = menuMapper.findMenuByRoleId(roleid);

        return menuByRoleId;
    }
Web层:添加部分MenuController :
   /*
  根据角色id查询该角色的菜单信息id
   */
    @RequestMapping("/findMenuByRoleId")
    public ResponseResult findMenuByRoleId(Integer roleId) {

        List<Integer> menuByRoleId = menuService.findMenuByRoleId(roleId);
        ResponseResult responseResult = new ResponseResult(true, 200, "根据角色id查询菜单信息成功", 
                                                           menuByRoleId);
        return responseResult;
    }

使用Postman测试接口
到这里对应菜单的回显就完成了
那么就到了最后操作保存分配的菜单了
需求分析:

在这里插入图片描述

在这里插入图片描述

开始进行保存的代码编写
Dao层:添加部分MenuMapper:
/*
    根据roleid清空中间表的对应关联关系
     */

    public void deleteRoleContextMenu(Integer roleid);
    /*
    为角色分配菜单
     */
    public void roleContextMenu(Role_menu_relation role_menu_relation);
添加部分MenuMapper.xml:
 <!--
    根据roleid清空中间表的关联关系
     -->
    <delete id="deleteRoleContextMenu" parameterType="int">
        DELETE FROM role_menu_relation WHERE role_id = #{id}
    </delete>

    <!--
    为角色分配菜单
    -->

    <insert id="roleContextMenu" parameterType="Role_menu_relation">
        insert into role_menu_relation values(null,#{menuId},#{roleId},
                        #{createdTime},#{updatedTime},#{createdBy},#{updatedby})
    </insert>
在写Service层之前,我们需要一个类
来存放前端传递过来的参数(当然也可以不使用,但这是为了以后的扩展,除非确定是固定的)
package com.lagou.domain;

import java.util.List;

/**
 *
 */
public class RoleMenuVo {
    private Integer roleId;

    private List<Integer> menuIdList;

    private String createdBy;
    
    private String updatedBy;
    //为什么加上createdBy和updatedBy变量
    //是因为表对应的字段的值是不能为空的(表的设计造成的),也就是说,我们需要添加对应数据
    //那么从这里我们也要知道,表的结构也是需要进行确定的,即熟悉一下

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public List<Integer> getMenuIdList() {
        return menuIdList;
    }

    public void setMenuIdList(List<Integer> menuIdList) {
        this.menuIdList = menuIdList;
    }

    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public String getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }
}

编写好后,接下了编写Service层的代码
Service层:添加部分MenuService及其实现类 :
/*
    为角色分配菜单,包含删除对应关联,和添加对应关联
    这里就体现出使用Service层的作用了
     */
    public void roleContextMenu(RoleMenuVo roleMenuVo);
 /*
    为角色分配菜单,包含删除对应关联,和添加对应关联
    这里就体现出使用Service层的作用了
     */
    @Override
    public void roleContextMenu(RoleMenuVo roleMenuVo) {

        //清空中间表的关系
        menuMapper.deleteRoleContextMenu(roleMenuVo.getRoleId());

        //遍历添加关联
        for (Integer menuId : roleMenuVo.getMenuIdList()) {
            Role_menu_relation role = new Role_menu_relation();
            role.setMenuId(menuId);
            role.setRoleId(roleMenuVo.getRoleId());
            role.setCreatedBy(roleMenuVo.getCreatedBy());
            role.setUpdatedby(roleMenuVo.getUpdatedBy());
            Date date = new Date();
            role.setCreatedTime(date);
            role.setUpdatedTime(date);
            menuMapper.roleContextMenu(role);
        }

        //这里体现出了使用service层的主要好处(主要的就是简便controller层的编写)
        //若不使用这个service层,那么就需要在controller层自己去调用dao层的方法
        //只有一个dao层方法那么还算好的,若有很多的dao层联系的方法
        //那么controller层的代码会非常的多,不好维护

    }
Web层:添加部分MenuController :
/*
    为角色分配菜单
     */

    @RequestMapping("/roleContextMenu")
    public ResponseResult roleContextMenu(@RequestBody RoleMenuVo roleMenuVo) {

        menuService.roleContextMenu(roleMenuVo);

        ResponseResult responseResult = new ResponseResult(true, 200, "角色分配菜单成功", null);
        return responseResult;
    }
使用Postman测试接口
删除角色
需求分析
需求:点击删除按钮,将选中的角色信息删除
Dao层:添加部分MenuMapper:
/*
    删除角色
     */
    public void deleteRole(Integer id);
添加部分MenuMapper.xml:
 <!--删除角色-->
    <delete id="deleteRole" parameterType="int">
        delete from roles
        where id = #{id}
    </delete>
Service层:添加部分MenuService及其实现类 :
/*
    删除角色
     */
    public void deleteRole(Integer id);
  /*
    删除角色
     */
    @Autowired
    private MenuMapper menuMapper;
    @Override
    public void deleteRole(Integer id) {
        //清空中间表关系
        menuMapper.deleteRoleContextMenu(id);

        roleMapper.deleteRole(id);

        //这个最好有顺序,因为我们通常需要先删除对应外键联系,然后删除主键
        //若没有外键关系,那么就可以直接删除主键
        //但是防止有外键联系及其数据的合理性
        //我们通常都会先删除对应外键或者有外键关系的字段(没有设置外键,但是当作外键看的字段)

    }
Web层:添加部分MenuController :
 /*
    删除角色
     */
    @RequestMapping("/deleteRole")
    //要注意:虽然对应整型可以识别01,02等类型的数据,实际上就是1,2
    //但使用json格式时,会被检查,也就是说当前端传递的json格式的数是01,02等整型类型时
    //服务器会处理不了这个json,即400报错,请求参数错误
    public ResponseResult deleteRole(Integer id) {
        roleService.deleteRole(id);
        ResponseResult responseResult = new ResponseResult(true, 200, "删除角色成功", null);
        return responseResult;
    }
使用Postman测试接口
权限管理(菜单模块)实现 :
菜单列表查询
需求分析
需求:点击菜单列表,对菜单信息进行列表展示

在这里插入图片描述

Dao层:添加部分MenuMapper:
/*
    查询所有菜单列表
     */
    public List<Menu> findAllMenu();
添加部分MenuMapper.xml:
<!--
    查询所有菜单列表
    -->
    <select id="findAllMenu" resultType="Menu">
        select * from menu;
    </select>
Service层:添加部分MenuService及其实现类 :
   /*
  查询所有菜单列表
   */
    public List<Menu> findAllMenu();
 /*
 查询所有菜单列表
  */
    @Override
    public List<Menu> findAllMenu() {
        List<Menu> allMenu = menuMapper.findAllMenu();
        return allMenu;
    }
Web层:添加部分MenuController :
  /*
    查询所有菜单列表
     */
    @RequestMapping("/findAllMenuList")
    public ResponseResult findAllMenu() {
        List<Menu> allMenu = menuService.findAllMenu();
        ResponseResult responseResult = new ResponseResult(true, 200, "查询所有菜单信息成功", allMenu);
        return responseResult;
    }
使用Postman测试接口
回显菜单信息
需求分析
需求:
点击添加菜单按钮,跳转到添加菜单页面,回显当前添加菜单可以选择的上级菜单信息
点击编辑按钮,也进行对应回显操作(但有对应其他的信息,也可以说是所有信息,即如包括上级菜单信息和本身其他信息)

在这里插入图片描述

在这里插入图片描述

具体分析:

在这里插入图片描述

在这里插入图片描述

根据需求,我们发现,添加和修改都有对应回显,这个同样的回显都是查询所有父菜单信息
虽然我们使用了对应父子菜单查询结果当作对应根据id查询角色信息来进行回显,但我们可以不使用子菜单信息
实际上你也可以自己创建一个查询语句进行调用
但这里我们就进行复用了,而复用了,那么对应父菜单回显sql语句就不需要编写了
也就是说,寻常的根据parent_id查询父菜单信息也就不需要写了
虽然修改和添加有同样的回显操作,但修改却需要回显对应具体其他信息
对应修改回显操作的代码编写如下:
Dao层:添加部分MenuMapper:
 /*
  根据id查询菜单信息
   */
    public Menu findMenuById(Integer id);
添加部分MenuMapper.xml:
 <!--根据id查询菜单信息-->
    <select id="findMenuById" parameterType="int" resultType="Menu">
        select * from menu where id = #{id}
    </select>
Service层:添加部分MenuService及其实现类 :
/*
    根据id查询菜单信息
     */
    public Menu findMenuById(Integer id);
/*
    根据id查询菜单信息
     */
    @Override
    public Menu findMenuById(Integer id) {
        Menu menuById = menuMapper.findMenuById(id);
        return menuById;
    }
编写好后,我们可以将添加和修改的回显信息放在一个方法里进行判断操作
而不用分开在Web层创建两个方法了(当然也可以进行分开)
之所以有这样的方式,在表里面可以有体现,如id
菜单信息里面id一般都是大于等于1的整数,这是表的设计,也就是说,基本是可以使用特有的负数(如-1)来进行判断操作
Web层:添加部分MenuController :
  /*
    回显菜单信息,父菜单信息或者本身的所有菜单信息
     */
    @RequestMapping("/findMenuInfoById")
    public ResponseResult findMenuInfoById(Integer id) {

        ResponseResult responseResult;
        HashMap<Object, Object> objectObjectHashMap;
        List<Menu> subMenuListByPid = menuService.findSubMenuListByPid(-1);
        //这里不可以使用id当作参数,主要是对应判断不与这个参数有联系,只是用来确定添加和修改操作
        //所有对应修改操作里,也会使用这个方法,即不能使用id当作参数

        //判断id的值是否是-1,-1则是添加操作回显,否则是修改操作回显,用这种形式来决定添加和修改的回显不同
        if (id == -1) {

            //添加 回显信息中只需要查询menu所有父菜单信息

            objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("menuInfo", null);
            objectObjectHashMap.put("parentMenuList", subMenuListByPid);
            responseResult = new ResponseResult(true, 200, "添加菜单信息回显成功", 
                                                objectObjectHashMap);
        } else {
            //修改操作 回显所有的menu信息,即所有字段信息(包括父菜单信息和普通的字段信息,也就是所有的信息)
            Menu menu = menuService.findMenuById(id);
            objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("menuInfo", menu);
            objectObjectHashMap.put("parentMenuList", subMenuListByPid);
            responseResult = new ResponseResult(true, 200, "修改菜单信息回显成功", 
                                                objectObjectHashMap);


        }

        return responseResult;
    }
使用Postman测试接口
这下我们就将对应添加和修改的回显操作完毕了,接下来我们进行添加和修改的直接操作
添加操作:
Dao层:添加部分MenuMapper:
/*
    添加菜单
     */
    public void saveMenu(Menu menu);
添加部分MenuMapper.xml:
 <!--添加菜单-->
    <insert id="saveMenu" parameterType="Menu">
        insert into menu values(null,#{parentId},#{href},#{icon},#{name},#{description},#{orderNum},
                                #{shown},#{level},#{createdTime},#{updatedTime},
                                #{createdBy},#{updatedBy})
    </insert>

Service层:添加部分MenuService及其实现类 :
  /*
    添加菜单
     */
    public void saveMenu(Menu menu);
 /*
   添加菜单
    */
    @Override
    public void saveMenu(Menu menu) {
        Date date = new Date();
        menu.setCreatedTime(date);
        menu.setUpdatedTime(date);
        menuMapper.saveMenu(menu);

    }
Web层:添加部分MenuController :
 /*
    添加菜单(后续会进行修改扩展)
     */
    @RequestMapping("/saveOrUpdateMenu")
    public ResponseResult saveOrUpdateMenu(@RequestBody Menu menu){
        menuService.saveMenu(menu);

        ResponseResult responseResult = new ResponseResult(true, 200, "添加菜单成功", null);
        return responseResult;
    }
回显操作已经操作完了,所有可以直接进行修改操作了
使用Postman测试接口
修改操作:
Dao层:添加部分MenuMapper:
 /*
    修改菜单
     */
    public void updateMenu(Menu menu);
添加部分MenuMapper.xml:
 <!--修改菜单-->
    <update id="updateMenu" parameterType="Menu">
        update menu
        <trim prefix="SET" suffixOverrides=",">
            <if test="parentId != 0">
                parent_id = #{parentId},
            </if>
            <if test="href != null and href != ''">
                href = #{href},
            </if>
            <if test="icon != null and icon != ''">
                icon = #{icon},
            </if>
            <if test="name != null and name != ''">
                name = #{name},
            </if>
            <if test="description != null and description != ''">
                description = #{description},
            </if>
            <if test="orderNum != 0">
                order_num = #{orderNum},
            </if>
            <if test="shown != 0">
                shown = #{shown},
            </if>
            <if test="level != 0">
                level = #{level},
            </if>
            <if test="createdTime != null">
                created_time = #{createdTime},
            </if>
            <if test="updatedTime != null">
                updated_time = #{updatedTime},
            </if>
            <if test="createdBy != null and createdBy != ''">
                created_by = #{createdBy},
            </if>
            <if test="updatedBy != null and updatedBy != ''">
                updated_by = #{updatedBy}
            </if>

        </trim>
        <where>
            <if test="id != null">
                id = #{id}
            </if>
        </where>

    </update>
    <!--
        这里说明一下:对应替换(如#{},上面动态sql的对应字段),是使用get方法,若没有get方法则直接赋值
        且无论是get后面名称还是直接赋值,首字母大小写忽略
        其中在进行动态sql替换时,若是Date类型
        由于没有方法可以变成字符串(不像其他基本数据类型一样可以变成字符串)
        那么只能直接替换,所以在进行判断时,对应的判断是不能出现的,如上面的updatedTime != null不能是
        如上面的updatedTime != null and updatedTime != '',后面的''判断不能加上,因为Date不可以判断字符串
        因为不是一个类型,所以会出现判断错误,即500服务器内部报错
        出现invalid comparison: java.util.Date and java.lang.String
        除非没有进行判断,即若是null,那么就是false,而与的操作,只要前面是false,那么后面就不会判断了
        也就不执行了,即不会出现抛出错误
        而Date在进行#{}替换时,则会被框架解析成对应字符串,默认是yyyy-MM-dd HH:mm:ss的解析
        所以可以传递Date到sql语句里面,因为底层帮我们操作了

可以发现他也是普通的进行比较,所以也不能出现不能比较的,如’‘(中文分号),java识别不了,即报出现非法字符的错误
当然必须要执行到这里才会有错误的,这是java在运行期间抛出错误的主要形式
        -->
Service层:添加部分MenuService及其实现类 :
  /*
   修改菜单
    */
    public void updateMenu(Menu menu);
 /*
  修改菜单
   */
    @Override
    public void updateMenu(Menu menu) {
        menu.setUpdatedTime(new Date());
        menuMapper.updateMenu(menu);
    }
Web层:修改部分MenuController (扩展saveOrUpdateMenu方法):
/*
    添加菜单(后续会进行修改扩展)
     */
    @RequestMapping("/saveOrUpdateMenu")
    public ResponseResult saveOrUpdateMenu(@RequestBody Menu menu){
        ResponseResult responseResult;
        if(menu.getId() == null){
            menuService.saveMenu(menu);

           responseResult = new ResponseResult(true, 200, "添加菜单成功", null);

        }else{
            menuService.updateMenu(menu);
            responseResult = new ResponseResult(true, 200, "修改菜单成功", null);

        }
        return responseResult;
    }
使用Postman测试接口
最后我们来实现删除菜单的操作
Dao层:添加部分MenuMapper:
 /*
    删除菜单
     */
    public void deleteMenu(Integer id);
添加部分MenuMapper.xml:
  <!--删除菜单-->
    <delete id="deleteMenu" parameterType="int">
        delete from menu where id = #{id}
    </delete>
Service层:添加部分MenuService及其实现类 :
/*
   删除菜单
    */
    public void deleteMenu(Integer id);
/*
   删除菜单
    */
    @Override
    public void deleteMenu(Integer id) {
        menuMapper.deleteMenu(id);

    }
Web层:添加部分MenuController :
/*
    删除菜单
     */
    @RequestMapping("/deleteMenu")
    public ResponseResult deleteMenu(Integer id){
        menuService.deleteMenu(id);
        ResponseResult responseResult = new ResponseResult(true, 200, "删除菜单成功", null);
        return responseResult;
    }
使用Postman测试接口
权限管理(资源模块)实现 :
资源分类,分页及其多条件查询
需求分析
需求:资源列表及分页多条件组合查询

在这里插入图片描述

在这里插入图片描述

根据需求我们需要5个参数,那么就创建一个类来存放
ResourceVo类:
package com.lagou.domain;

/**
 *
 */
public class ResourceVo {

    private Integer currentPage;
    private Integer pageSize;
    private String name;
    private Integer categoryId;
    private String url;

    public Integer getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(Integer currentPage) {
        this.currentPage = currentPage;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

接下来进行代码编写:
首先我们操作分页多条件查询
Dao层:ResourceMapper:
package com.lagou.dao;

import com.lagou.domain.Resource;
import com.lagou.domain.ResourceVo;

import java.util.List;

/**
 *
 */
public interface ResourceMapper {
    /*
    资源分页&多条件查询
     */
    public List<Resource> findAllResourceByPage(ResourceVo resourceVo);
    //如果resultType中的返回类型与Resource不一致,基本也不会报错,因为在运行期间,默认是Object了
}

对应映射配置文件(ResourceMapper.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lagou.dao.ResourceMapper">
    <!--资源分页&多条件查询-->
    <select id="findAllResourceByPage" parameterType="ResourceVo" resultType="Resource">
        select * from resource
        <where>

            <if test="name != null and name != ''">
                and name like '%${name}%'
            </if>
            <if test="url != null and url != ''">
                and url = #{url}
            </if>
            <if test="categoryId != null">
                and category_id = #{categoryId}
            </if>
        </where>
    </select>
</mapper>
Service层:ResourceService及其实现类 :
package com.lagou.service;

import com.github.pagehelper.PageInfo;
import com.lagou.domain.Resource;
import com.lagou.domain.ResourceVo;

import java.util.List;

/**
 *
 */
public interface ResourceService {
    /*
   资源分页&多条件查询
    */
    public PageInfo<Resource> findAllResourceByPage(ResourceVo resourceVo);
}

package com.lagou.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lagou.dao.ResourceMapper;
import com.lagou.domain.Resource;
import com.lagou.domain.ResourceVo;
import com.lagou.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 */
@Service
public class ResourceServiceImpl implements ResourceService {

    @Autowired
    private ResourceMapper resourceMapper;

    /*
  资源分页&多条件查询
   */
    @Override
    public PageInfo<Resource> findAllResourceByPage(ResourceVo resourceVo) {

        //分页查询,参数一:当前页,参数二:每页显示条数
        PageHelper.startPage(resourceVo.getCurrentPage(), resourceVo.getPageSize());


        List<Resource> allResourceByPage = resourceMapper.findAllResourceByPage(resourceVo);


        PageInfo<Resource> resourcePageInfo = new PageInfo<>(allResourceByPage);

        return resourcePageInfo;
    }
}

Web层:ResourceController :
package com.lagou.controller;

import com.github.pagehelper.PageInfo;
import com.lagou.domain.Resource;
import com.lagou.domain.ResourceVo;
import com.lagou.domain.ResponseResult;
import com.lagou.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 */
@RestController
@RequestMapping("/resource")
public class ResourceController {

    @Autowired
    private ResourceService resourceService;

    /*
  资源分页&多条件查询
   */
@RequestMapping("/findAllResourceByPage")
    public ResponseResult findAllResourceByPage(@RequestBody ResourceVo resourceVo) {
        PageInfo<Resource> allResourceByPage = resourceService.findAllResourceByPage(resourceVo);
        ResponseResult responseResult = new ResponseResult(true, 200, "资源信息分页多条件查询成功", 
                                                           allResourceByPage);

        return responseResult;
    }
}

使用Postman测试接口
分页多条件查询后,我们再操作资源分类查询
代码编写如下:
Dao层:添加部分ResourceMapper:
   /*
    查询所有资源分类
     */
    public List<ResourceCategory> findAllResourceCategory();
添加部分ResourceMapper.xml:
 <!--查询所有资源分类-->
    <select id="findAllResourceCategory" resultType="ResourceCategory">
        select * from resource_category
    </select>
Service层:添加部分ResourceService及其实现类 :
   /*
  查询所有资源分类
   */
    public List<ResourceCategory> findAllResourceCategory();
    /*
查询所有资源分类
*/
    @Override
    public List<ResourceCategory> findAllResourceCategory() {
        List<ResourceCategory> allResourceCategory = resourceMapper.findAllResourceCategory();
        return allResourceCategory;
    }
Web层:添加部分ResourceController :
    /*
 查询所有资源分类
  */
    @RequestMapping("/findAllResourceCategory")
    public ResponseResult findAllResourceCategory(){
        List<ResourceCategory> allResourceCategory = resourceService.findAllResourceCategory();
        ResponseResult responseResult = new ResponseResult(true, 200, "查询所有资源分类信息成功", 
                                                           allResourceCategory);
        return responseResult;
    }
使用Postman测试接口
在71章博客里说过,有些代码编写,需要权限模块说明后,才进行编写,现在我们就开始编写这些代码
登陆及动态菜单展示 :
动态显示图解:

在这里插入图片描述

根据权限显示对应的管理模块,后面会再次说明
登陆 :
需求分析
需求:输入用户名密码,点击登陆按钮,进行用户登陆

在这里插入图片描述

在进行登录之前,说明一下加密算法MD5介绍
什么是MD5:
MD5加密全程是Message-Digest Algoorithm 5(信息-摘要算法)
它对信息进行摘要采集,再通过一定的位运算,最终获取加密后的MD5的对应字符串
MD5有哪些特点:
MD5加密的特点主要有以下几点:
1:针对不同长度待加密的数据、字符串等等,其都可以返回一个固定长度的MD5加密字符串(通常32位的16进制字符串)
2:其加密过程几乎不可逆,所以登录的密码也会在服务器里进行加密来操作查询,使得登录是否成功
当然加盐也是一样,只要有不可逆的操作时,对应登录的对应输入,如密码
也就会在服务器里进行加密,然后查询数据库,进行判断是否正确)
除非维护一个庞大的Key-Value数据库来进行碰撞破解或者暴力破解
碰撞破解:也就是查询对应数据库是否有对应数据,暴力破解:将数据库对应的key-value依次进行试验
如用户名和密码的多次试验(暴力破解),或者对应的MD5加密后的数据对应(碰撞破解)
不同的场景,对应的key-value不同,看如下解释:
而对于MD5加密后的数据对应解释(碰撞破解,key一般是密码,value一般是加密后的数据):
比如我们通过key,经过MD5加密得到了value,如123456lagou经过MD5加密变成f00485441dfb815c75a13f3c3389c0b9
然后将这一组数据保存到数据库里面(当然若是在开发中,存放在数据库的就是用户名和f00485441dfb815c75a13f3c3389c0b9)
即将123456lagou和f00485441dfb815c75a13f3c3389c0b9存放在数据库中,这样存放后
我们就可以通过加密后的f00485441dfb815c75a13f3c3389c0b9得到123456lagou这个真正的密码
我们通过这样的操作,进行多组的存放
即入侵对方数据库后,根据数据库MD5加密的value找到对应的key,并不是逆向破解MD5算法
而是一个庞大的MD5的key-value数据库查询,即很多个key-value来进行操作
对于用户名和密码的多次试验解释(暴力破解,key一般是用户名,value一般是密码):
我们可以通过同样的用户名和密码的key-value数据库来进行暴力破解
但现在的用户名和密码的碰撞操作基本有次数限制,实际上也可以跳过这个限制(但很难),所以基本不做考虑
我们发现,在没有对应的key-value庞大数据库时
就算我们得到了对应MD5加密的value,基本也得不到对应的key(密码)
所以几乎无法解开,当然无论是什么加密,那么一定有对应的逆过程,所以这里说的是几乎
因为MD5算法好像被某软件小组破解了,早在2010年,美国软件工程学会也认为MD5算法已被破解,但普通人是基本不可能破解的
2:运算简便,且可实现方式多样,而由于有庞大的Key-Value数据库来进行碰撞破解
所以我们通常需要通过一定的处理方式来避免碰撞破解
如:
加盐(专业术语,加盐:给没有加密的字符串拼接上随机字符串,一般随机的字符串不会出现相同的,如操作UUID等等)
强加密(强加密:对加密再次进行加密,如给加密的字符串进行加盐)
对于强加密的解释如下:
即在原来的加密字符串上拼接上随机的字符串进行加密
也可以操作拼接方式,而不只是直接拼接在后面,一般可能会拼接在后面
而拼接时,因为是在服务器里面操作的,所以这个拼接方式基本不可能被获得(除非内部人员)
使得原来加密字符串变长,并将得到的随机字符串存放在对应用户名的那一条数据的某个字段里面
而在登录时,登录的对应密码在服务器中也要进行加密
因为MD5加密的对应密码字符串基本不可逆,所以我们需要在加密后,进行对应查询(这里使用了加盐)
然后取出对应存放好的随机字符串与加密后的输入的字符串进行对应拼接(服务器的拼接方式)
使得判断是否与对应拼接后的字符串一致
若一致,则登录成功,而由于是随机的(自己定义随机出现的字符串)
那么就算被别人入侵了数据库得到了对应随机出来的字符串后
就算使用的是超级庞大的MD5的key-value数据库(经过随机字符串的拼接),那么也是很难破解的
因为就算知道了对应拼接的随机字符串数,也要知道对应的拼接方式
这样的基数(数据库的总条数)就非常大了,所以基本很难碰撞破解
因为对应的key-value可能存放不了这么多数据,或者没有对应的key-value
所以,强加密是可以进行更加的安全效果的
对于加盐的的解释如下:
加盐的安全效果与强加密差不多,都是可以进行更加的安全效果的
与强加密不同的是,这里是给没有加密的字符串进行随机字符串拼接
但我们可以发现,若随机字符串不拼接在后面,而是随机字符串的每个字符进行整体拼接
那么使用加盐,增加的个数是对于原字符串和随机字符串的排列组合
而使用强加密,是加密字符串和随机字符串的排列组合
所以这两种实际上添加的个数是按照加密字符串和原字符串的位数来决定的
即添加的个数也基本都是不同的,但我们可以发现,只要原字符串大于MD5加密的32位字符串位数,加盐的排列组合就多
即这时我们可以使用加盐,若是直接拼接在后面的,那么强加密的模式的随机字符串是没有作用的
因为只需要对前32位进行查询即可,而使用加盐,那么排列组合就是随机字符串的排列组合了
所以在不需要非常复杂的加密时
一般都是使用加盐,而不是强加密
因为只需要对前32位进行查询即可,而复杂的加密却需要随机字符串的每个字符都进行随机拼接
而不能直接拼接到后面,这是很麻烦的
如何使用那一种看你的操作,这里我们就直接使用加盐
因为一般不会进行复杂的加密,若使用复杂的加密,则上限高,但实际上对于原字符串一般都不会超过32位
那么由于都是使用随机的字符串,使得个数变多,且由于随机(自己定义的随机),那么对应的key-value也是基本很难找到的
因为不同的程序员使用的随机字符串获得方式也基本不会相同,也很难猜到会使用那种随机方式
而就算是拼基数(数据库的总条数),对于是最少32位排列组合也是非常大的
但要完全的安全,基本不可能,因为只要可以登录
那么就一定有对应的Key-Value数据(正好碰到,或者庞大的Key-Value数据库)
换言之就是能进去,那么就一定可以被破解,我们只能提高安全,但不能完全安全
4:对于一个固定的字符串,数字等等,MD5加密后的字符串是固定的(这是基本必须的,因为我们需要登录)
也就是说不管MD5加密多少次,都是同样的结果
加盐在注册时进行拼接,在登录时,是直接获得后再拼接
也就是说注册后,对应的随机字符串也就固定了
而由于是对加密字符串进行拼接,所以我们也可以称作加盐是加强MD5算法的操作
图解:

在这里插入图片描述

在这里插入图片描述

从图中看,登录时的对应密码的确在服务器里也进行加密了
Java代码中如何使用MD5:
添加依赖:
  <!--MD5(信息-摘要算法)依赖-->
        <dependency>
            <!--这个可以不需要-->
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <version>3.3.2</version>
           </dependency>
           <dependency>
               <!--进行加密的依赖报-->
             <groupId>commons-codec</groupId>
             <artifactId>commons-codec</artifactId>
             <version>1.3</version>
           </dependency>
添加工具类:
package com.lagou.utils;


import org.apache.commons.codec.digest.DigestUtils;

import java.util.UUID;

public class Md5 {

    public final static String md5key = "lagou";
    /**
     * MD5方法
     * @param text 明文 123456
     * @param key 密钥 lagou
     * @return 密文
     * @throws Exception
     */
    //进行注册的操作
    public static String md5(String text, String key) throws Exception {
        //加密后的字符串
        String encodeStr= DigestUtils.md5Hex(text+key); //这里拼接一个固定数
        System.out.println("MD5加密后的字符串为:encodeStr="+encodeStr);
        return encodeStr;
    }

    /**
     * MD5验证方法
     * @param text 明文
     * @param key 密钥
     * @param md5 密文
     * @return true/false
     * @throws Exception
     */
    //进行登录的操作,获得数据库的密文,进行判断是否一致
    public static boolean verify(String text, String key, String md5) throws Exception {
        //根据传入的密钥进行验证
        String md5Text = md5(text, key);
        if(md5Text.equalsIgnoreCase(md5))
        {
            System.out.println("MD5验证通过");
            return true;
        }
        return false;
    }

    //上面并没有用到加盐,即加随机数字符串

    //测试登录和注册
    public static void main(String[] args) throws Exception {

        //注册 用户名:tom 密码:123456
        //注册用户时:将明文密码转换成密文密码
        String s = md5("123456", md5key);
        System.out.println(s);

        //登录 用户名:tom 密码:123456
        //登录时:将明文密码转换成密文密码
        boolean verify = verify("123456", md5key, s);
        System.out.println(verify);


    }

}

到了这里,我们就可以来实现登录操作了:
首先再分析一下登录代码编写:

在这里插入图片描述

这时用户的操作,所以操作的是User类,即对应的接口也是User的
Dao层:添加部分UserMapper:
/*
    用户登录(根据用户名查询具体用户信息,就是将查询的与密码进行对应)
     */
    public User login(User user);
添加部分UserMapper.xml:
 <!--根据用户名查询用户信息,用户名基本是不能一致的-->
<select id="login" parameterType="User" resultType="User">
        select * from user where phone = #{phone}
</select>
Service层:添加部分UserService及其实现类 :
 /*
    用户登录(根据用户名查询具体用户信息,就是将查询的与密码进行对应)
     */
    public User login(User user) throws Exception;
 /*
    用户登录
     */
    @Override
    public User login(User user) throws Exception {
        User pass = userMapper.login(user);

        //判断用户是否存在,以及对应的密码是否正确,只要有一个没有满足,那么就返回null
        //表示用户名不存在或者密码错误(用户名或密码错误)
        if(pass!=null && Md5.verify(user.getPassword(),"lagou",pass.getPassword())){
            return pass;
        }else {
            return null;

        }
    }
Web层:添加部分UserController :
 /*
    用户登录
     */
    @RequestMapping("/login")
    //前端是get请求,那么这里就不使用RequestBody了,直接默认封装
    public ResponseResult login(User user, HttpServletRequest request) throws Exception {
        ResponseResult responseResult;
        User login = userService.login(user);
        if(login!=null){
            //保存用户id(不是用户名,是他们的唯一标识id,这里的用户名就是手机号)和access_token到session中

            HttpSession session = request.getSession();
            String access_token = UUID.randomUUID().toString();
            //对应的access_token的value值通常是不一致
            //若一致也没关系,反正会覆盖掉之前的access_token的value值
            //但不一致可以通过UUID来知道一些信息
            //如你登录的当前日期和时间(可能可以知道,可以进行操作,这里就不进行操作了)
            session.setAttribute("access_token",access_token);
            session.setAttribute("User_id",login.getId());
            //这样,当登录一次后,下次登录,可以通过session来进行免登录,这样的操作先不编写

            //将结果响应给前台
            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("access_token",access_token);
            objectObjectHashMap.put("User_id",login.getId());


            responseResult = new ResponseResult(true, 200, "用户登录成功",objectObjectHashMap);
        }else{

            //400表示错误的请求,这里我们直接给出这个信息,实际上并没有错误,这时人为的
            responseResult = new ResponseResult(true, 400, "用户名或密码错误", null);


        }
        return responseResult;

    }
使用Postman测试接口
在这里补充一下,我们改造一下上面的login方法
Web层:修改部分UserController (扩展login方法):
 /*
    用户登录
     */
    @RequestMapping("/login")
    //前端是get请求,那么这里就不使用RequestBody了,直接默认封装
    public ResponseResult login(User user, HttpServletRequest request) throws Exception {
        ResponseResult responseResult;
        User login = userService.login(user);
        if(login!=null){
            //保存用户id(不是用户名,是他们的唯一标识id,这里的用户名就是手机号)和access_token到session中

            HttpSession session = request.getSession();
            String access_token = UUID.randomUUID().toString();
            //对应的access_token的value值通常是不一致
            //若一致也没关系,反正会覆盖掉之前的access_token的value值
            //但会对权限的判断出现影响(一般用这个做权限判断) 
            //但不一致可以通过UUID来知道一些信息
            //如你登录的当前日期和时间(可能可以知道,可以进行操作,这里就不进行操作了)
            session.setAttribute("access_token",access_token);
            session.setAttribute("User_id",login.getId());
            //之所以存放这两个数据,其中token是为了进行权限的判断(一般的基本都是用来防止重复提交的)
            //先保存好
            //使得别人获取权限时,要与这个进行比较,而之所以不比较id,是防止id泄露
            //因为这个id是属于数据库的主键字段
            //一般我们都在除了必须要输入的数据是数据库的数据外(如登录操作),都不会显示出对应数据库的数据
            //而上面两个的存放都可以进行免登录,一般不会试验id进行,而是token进行,防止泄露
            //这样,当登录一次后,下次登录,可以通过session来进行免登录,这样的操作先不编写
            //如下次来时
        //判断对应id(也有可能不是id,如token)是否一样(实际上基本是一样的,主要是存不存在,即是否是null)
            //使得直接登录成功
            //而之所以存放id,是为了在判断成功后,在进行用户权限所对应的菜单获取
     

            //将结果响应给前台
            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("access_token",access_token);
            objectObjectHashMap.put("User_id",login.getId());
            //上面可以进行对应的回显操作,还有其他操作,这里就不说明了
            //而之所以需要进行封装到响应数据里,首先token是需要判断是否一致的,之所以不从服务器里获得
            //是为了再次进行判断,防止特殊情况
            //而id可以进行对应的回显,或者其他的方法调用


            //将查询出来的user,也存到map集合中(这里是login变量)
            //主要是为了使得用户登出时用到,到时候,就不可以免登录了,这样的操作先不编写
		    //如判断他的id是否与对应session存的id一样
            //基本是一样的,主要是存不存在,即是否是null
            //使得删掉对应key,那么免登录就不可以了
            //当然也可以操作回显的id
            objectObjectHashMap.put("user",login);
            
            //注意:无论什么情况,都只是为了方便操作,若你认为上面的操作不合理或者不需要,可以进行修改
            //实际上写上去也是没有什么坏处的,这都是为了加坑位,方便扩展(在不影响其他代码的情况下)


            responseResult = new ResponseResult(true, 1, "用户登录成功",objectObjectHashMap);
        }else{

            //400表示错误的请求,这里我们直接给出这个信息,实际上并没有错误,这时人为的
            responseResult = new ResponseResult(false, 400, "用户名或密码错误", null);


        }
        return responseResult;

    }
分配角色操作:
首先我们先查询出对应用户已经关联的角色
需求分析
需求:点击分配角色,将该用户所具有的角色信息进行回显

在这里插入图片描述

可以发现这个已经关联的角色只有两个,而后面的可以操作的角色前面有对应sql可以得到
所以我们现在编写查询对应用户关联的角色
Dao层:添加部分UserMapper:
/*
    根据用户id查询关联的角色信息
     */
    public List<Role> findUserRelationRoleById(Integer id);
添加部分UserMapper.xml:
<!--根据用户id查询关联的角色信息-->
<!--parameterType可以省略的,只是让你知道他的参数是什么,实际上并没有起到作用-->
    <select id="findUserRelationRoleById" parameterType="Integer" resultType="Role">
        SELECT * FROM roles r INNER JOIN user_role_relation ul ON r.id = ul.role_id WHERE ul.user_id = 
        #{userid}
    </select>
Service层:添加部分UserService及其实现类 :
 /*
    分配角色中查询用户关联的角色
     */
    public List<Role> findUserRelationRoleById(Integer id);
  /*
分配角色中查询用户关联的角色
 */
    @Override
    public List<Role> findUserRelationRoleById(Integer id) {
        List<Role> userRelationRoleById = userMapper.findUserRelationRoleById(id);

        return userRelationRoleById;
    }
Web层:添加部分UserController :
     /*
分配角色中查询用户关联的角色
 */
    @RequestMapping("/findUserRelationRoleById")
    public ResponseResult findUserRelationRoleById(Integer id){
        List<Role> userRelationRoleById = userService.findUserRelationRoleById(id);

        ResponseResult responseResult = new ResponseResult(true, 200, "查询用户关联角色成功", 
                                                           userRelationRoleById);
        return responseResult;
    }
使用Postman测试接口
分配角色
需求分析
需求:点击确定按钮,真正实现用户角色关联

在这里插入图片描述

具体分析:

在这里插入图片描述

Dao层:添加部分UserMapper:
 /*
    根据用户id清空中间表的对应关联关系
     */

    public void deleteUserContextRole(Integer id);
    /*
    分配角色(即添加对应用户和角色的关联关系)
     */
    public void userContextRole(User_Role_relation user_role_relation);
添加部分UserMapper.xml:
 <!--根据用户id清空中间表的对应关联关系-->
    <select id="deleteUserContextRole" parameterType="int">
        delete
        from user_role_relation
        where user_id = #{userid}
    </select>

    <!--分配角色(即添加对应用户和角色的关联关系)-->
    <insert id="userContextRole" parameterType="User_Role_relation">
        insert into user_role_relation
        values (null, #{userId}, #{roleId},
                #{createdTime}, #{updatedTime}, #{createdBy}, #{updatedBy})
    </insert>
再编写Service层之前,我们需要创建一个类来存放对应参数
创建UserRoleVo类:
package com.lagou.domain;

import java.util.List;

/**
 *
 */
public class UserRoleVo {

    private List<Integer> roleIdList;
    private Integer userId;

    public List<Integer> getRoleIdList() {
        return roleIdList;
    }

    public void setRoleIdList(List<Integer> roleIdList) {
        this.roleIdList = roleIdList;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }
}

接下来我们来编写Service层代码:
Service层:添加部分UserService及其实现类 :
/*
   分配角色(即添加对应用户和角色的关联关系),包含删除对应用户与角色的关联
    */
    public void userContextRole(UserRoleVo userRoleVo);
 /*
  分配角色(即添加对应用户和角色的关联关系),包含删除对应用户与角色的关联
   */
    @Override
    public void userContextRole(UserRoleVo userRoleVo) {
        //根据用户id清空中间表关系
        userMapper.deleteUserContextRole(userRoleVo.getUserId());

        for(Integer roleId:userRoleVo.getRoleIdList()){
            User_Role_relation user_role_relation = new User_Role_relation();
            user_role_relation.setUserId(userRoleVo.getUserId());
            user_role_relation.setRoleId(roleId);
            Date date = new Date();
            user_role_relation.setCreatedTime(date);
            user_role_relation.setUpdatedTime(date);
            user_role_relation.setCreatedBy("system");
            user_role_relation.setUpdatedBy("system");

            userMapper.userContextRole(user_role_relation);
        }
    }
Web层:添加部分UserController :
  /*
 分配角色(即添加对应用户和角色的关联关系),包含删除对应用户与角色的关联
  */
    @RequestMapping("/userContextRole")
    public ResponseResult userContextRole(@RequestBody UserRoleVo userRoleVo){
        userService.userContextRole(userRoleVo);

        ResponseResult responseResult = new ResponseResult(true, 200, "分配角色成功", null);
        return responseResult;
    }
使用Postman测试接口
动态菜单显示
需求分析
需求:登陆成功后,根据用户所拥有的权限信息,进行菜单列表动态展示

在这里插入图片描述

具体分析:

在这里插入图片描述

在这里插入图片描述

对上图分析,我们发现要在Dao层编写四个方法,但其中的第一个方法我们已经编写过了
虽然前面编写过的方法中,有实现了父子菜单的方法,和根据角色id查询所有菜单id的方法
但却没有只查询父菜单的方法和只查询子菜单的方法,所以复用不到,那么为什么我们不使用实现了父子菜单的方法的结果呢
主要是因为实现了父子菜单的方法的结果只是给出对应权限的关系,而不能说明这个权限是否是这个用户的
当然了,你也可以利用他的结果的菜单id,来进行关联,但发现,还不如分开的两个方法,因为实现了父子菜单的方法是嵌套的
且对应的sql编写虽然简单了(只需要编写一个方法,而不需要编写两个了)
但后台还需要对应sql语句来判断封装
如判断对应id是否相等,相等则进行新的list集合封装,同样的则进行覆盖,这样就得到了对应的用户的权限的父子菜单信息了
但这里需要我们编写后台逻辑代码,这是非常麻烦的,所以一般能使用sql语句能做到的事情,后台尽量少做
使用sql语句即可以方便,也可以更好维护,更加的可以减少逻辑代码(基本都是这样,但也有特殊,如你的sql语句不好)
如查询所有,你偏偏就只写查询一条语句的代码,然后通过循环1到100(或者更大的)的id来进行多次查询每条语句
直到查询为null时,才结束,这是非常不好的使用sql语句
即这里我们就编写第二个和第三个方法
第四个也需要编写,没有复用的,即我们就编写后面三个方法
Dao层:添加部分UserMapper:
/*
    根据角色id,查询角色所拥有的顶级菜单(-1),由于角色不止一个,那么权限也就需要合并
    所以参数是list集合,用来传递多个角色id,使得合并权限,即一般sql语句使用in操作
    角色对应了多个菜单
     */
    public List<Menu> findParentMenuByRoleId(List<Integer> ids);
	//前面的findUserRelationRoleById方法可以通过用户id获得对应的角色信息,即可以有List<Integer> ids出现

    /*
    根据Pid(父菜单id),查询子菜单信息
     */
    public List<Menu> findSubMenuByPid(Integer pid);

    /*
    获取用户拥有的资源信息(实际上就是获取角色拥有的资源信息,但因为关联,所以也可说明是用户拥有)
     */
    public List<Resource> findResponseByRoleId(List<Integer> ids);

添加部分UserMapper.xml:
 <!--根据角色id,查询角色所拥有的顶级菜单(-1),由于角色不止一个,所以参数是list集合
    角色对应了多个菜单
前面的findUserRelationRoleById方法可以通过用户id获得对应的角色信息,即可以有List<Integer> ids出现
-->
    <!--List集合在Mybatis中也有默认的别名,如list(别名忽略大小写)-->
    <select id="findParentMenuByRoleId" parameterType="list" resultType="Menu">
        SELECT DISTINCT m.*
        FROM roles r
        INNER JOIN role_menu_relation rl ON r.id = rl.role_id
        INNER JOIN menu m ON m.id = rl.menu_id
        WHERE m.parent_id = -1 AND r.id IN
        <foreach collection="list" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
        <!--
           collection : 代表要遍历的集合元素,通常写collection或者list
           而之所以可以使用collection,是因为多态
           其他也可以,具体看百度,如array使用数组
           open : 代表语句的开始部分
           close : 代表语句的结束部分
           item : 代表遍历结合中的每个元素,生成的变量名,使用的#{}或者${}替换必须与这个名称一样,否则报错
           separator: 分隔符
                     -->

        <!--
        根据条件得到对应菜单的信息,然后对-1操作,得到的是父菜单信息,最后进行in操作(用户对应的所有角色id)
        使得角色的权限合并,然后去重,使得出现了这个用户的所有权限
        至于为什么说合并,举个例子:因为用户管理员和超级管理员都有用户的权限
        那么不可能出现两个对应权限的父菜单(用户管理)吧,所以就进行去重,因为我们只需要一个对应的父菜单
        -->
    </select>

    <!--根据Pid(父菜单id),查询子菜单信息-->
    <select id="findSubMenuByPid" parameterType="int" resultType="Menu">
        SELECT * FROM menu m WHERE m.parent_id = #{pid};
        <!--得到了对应父菜单的信息,那么就可以通过父菜单的id得到对应子菜单-->
    </select>

    <!--获取用户拥有的资源信息(实际上就是获取角色拥有的资源信息,但因为关联,所以也可说明是用户拥有)-->
    <select id="findResponseByRoleId" parameterType="list" resultType="Resource">
        SELECT DISTINCT rs.* FROM roles r INNER JOIN role_resource_relation rr ON r.id = rr.role_id
        INNER JOIN resource rs ON rs.id = rr.resource_id
        WHERE r.id IN
        <foreach collection="list" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </select>
    <!--先查询对应角色的资源信息,但角色有很多个,需要合并对应的资源权限-->
Service层:添加部分UserService及其实现类 :
/*
    获取用户权限(有四个方法的调用,其中一个是复用的,其他三个是刚刚编写的方法)
    这里我们直接在Service层里将数据封装到ResponseResult里面了
    当然你也可以在Controller层里进行操作封装
    将我们操作的map集合返回就可以了
    封装:可以说是对应有参构造或者set方法,或者其他方法,使得可以赋值的操作,都可以叫做封装

     */
    public ResponseResult getUserPermissions(Integer userid);
    //会取出用户id当成参数
 /*
    获取用户权限(有四个方法的调用,其中一个是复用的,其他三个是刚刚编写的方法)
    这里我们直接封装到ResponseResult里面了
    当然你也可以在Controller层里进行操作
     */
    @Override
    //会取出用户id当成参数
    public ResponseResult getUserPermissions(Integer userid) {
        //获取当前用户拥有的角色
        List<Role> userRelationRoleById = userMapper.findUserRelationRoleById(userid);
        
        //获取角色id保存到list集合中
        ArrayList<Integer> roleIds = new ArrayList<>();

        for (Role role : userRelationRoleById) { 
            roleIds.add(role.getId());
        }
        //userRelationRoleById没有值不循环,也就不会运行
        //而对应的sql中,若()里面没有值,则会报错,所以,对应的用户必须有对应的角色,否则是执行不了的
        //而这样的操作,通常是表的问题,一般用户都会有对应角色的,因为注册的操作,但这里并没有编写注册
        //所以你需要去数据库里进行数据的添加
        

        //根据角色id获取父菜单权限
        List<Menu> parentMenuByRoleId = userMapper.findParentMenuByRoleId(roleIds);

        //查询父菜单关联的子菜单
        for(Menu menu:parentMenuByRoleId){
            List<Menu> subMenuByPid = userMapper.findSubMenuByPid(menu.getId());
            menu.setSubMenuList(subMenuByPid);
        }

        //获取资源信息
        List<Resource> responseByRoleId = userMapper.findResponseByRoleId(roleIds);

        //封装数据并返回
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("menuList",parentMenuByRoleId);
        hashMap.put("resourceList",responseByRoleId);

        //这里直接在Service层将数据封装到ResponseResult里面了
        ResponseResult responseResult = new ResponseResult(true, 200, "获取用户权限信息成功", 
                                                           hashMap);

        return responseResult;
    }
Web层:添加部分UserController :
/*
    获取用户权限,进行菜单动态展示
     */
    @RequestMapping("/getUserPermissions")
    public ResponseResult getUserPermissions(HttpServletRequest request){

        //获取请求头中的token(登录中进行回显的对应token)
        String header_token = request.getHeader("Authorization");

        //获取session中的token
        String access_token = (String) request.getSession().getAttribute("access_token");

        //判断token是否一致
        ResponseResult responseResult;
        if(header_token.equals(access_token)){

            //获取用户id
            Integer user_id = (Integer) request.getSession().getAttribute("User_id");
            responseResult = userService.getUserPermissions(user_id);

        }else{
            responseResult = new ResponseResult(false, 400, "获取菜单信息失败", null);

        }
        return responseResult;

    }
使用Postman测试接口
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值