前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SSM 单体框架 - 教育平台后台管理系统:权限模块开发

SSM 单体框架 - 教育平台后台管理系统:权限模块开发

作者头像
RendaZhang
发布2020-09-24 11:04:09
1.6K0
发布2020-09-24 11:04:09
举报
文章被收录于专栏:RendaRendaRenda

权限概念介绍

权限:权利(能做的)和限制(不能做的),在权限范围内做好自己的事情

认证:验证用户名密码是否正确的过程

授权:对用户所能访问的资源进行控制(动态显示菜单、URL 级别的权限控制)

实现权限系统的原因

首先系统需要进行登陆才能访问

其次不同登陆用户要有不同的权利,而且要有不同的菜单(例如财务经理针对系统中财务相关模块进行操作,人事经理针对系统中人事模块进行操作)

权限控制基本原理

ACL(Access Control Lists)

ACL 是最早也是最基本的一种访问控制机制,它的原理非常简单:每一项资源,都配有一个列表,这个列表记录的就是哪些用户可以对这项资源执行 CRUD 中的哪些操作。当系统试图访问这项资源时,会首先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。总得来说,ACL 是一种面向资源的访问控制模型,它的机制是围绕“资源”展开的。

RBAC(Role-Based Access Control)

RBAC 是基于角色的访问控制,通过用户的角色来确定用户能否针对某项资源进行某项操作。RBAC 相对于 ACL 最大的优势就是它简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来,而用户与权限变成了间接关联。RBAC 模型使得访问控制,特别是对用户的授权管理变得非常简单和易于维护,因此有广泛的应用。

规则一:每个登陆的用户,可以有多个角色

规则二:每个角色又可以拥有多个权限(包含菜单和资源)

ACL:用户 -> 权限
RBAC:用户 -> 角色 -> 权限
权限系统经典五张表

用户表、角色表、权限表、用户角色中间表、角色权限中间表

权限模块功能分析

权限模块主要细分为角色模块、菜单模块、资源模块,将针对细分的三个模块进行具体功能实现,同时会完成用户登陆、用户关联角色及动态菜单显示

权限模块管理

实现以下功能

  • 角色列表 & 条件查询(角色模块)
  • 分配菜单(角色模块)
  • 删除角色(角色模块)
  • 菜单列表查询(菜单模块)
  • 查询菜单信息回显(菜单模块)
  • 资源分页&多条件查询(资源模块)
  • 用户登陆(用户模块)
  • 动态菜单展示(权限模块)
  • 用户关联角色(用户模块)

权限模块表设计

数据库表
  • user 用户表
  • roles 角色表
  • menu 菜单表
  • resource 资源表
  • resource_category 资源分类表
  • user_role_relation 用户角色中间表
  • role_menu_relation 角色菜单中间表
  • role_resource_relation 角色资源中间表
表关系介绍
ER 图

多个用户对多个角色

权限表由菜单表和资源表组成;菜单表和资源表对权限表进行了细粒度划分

多个角色对多个权限表;用户表和权限表没有直接关联,是通过角色表进行了间接关联

一个资源分类表对多个资源表

数据实体描述

角色表

CREATE TABLE `roles` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `code` VARCHAR(100) NOT NULL COMMENT '角色code',
  `name` VARCHAR(200) DEFAULT NULL COMMENT '角色名称',
  `description` VARCHAR(500) DEFAULT NULL COMMENT '简介',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

菜单表

CREATE TABLE `menu` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `parent_id` INT(11) NOT NULL COMMENT '父菜单id',
  `href` VARCHAR(200) DEFAULT NULL COMMENT '菜单路径',
  `icon` VARCHAR(200) DEFAULT NULL COMMENT '菜单图标',
  `name` VARCHAR(200) DEFAULT NULL COMMENT '菜单名称',
  `description` VARCHAR(500) DEFAULT NULL COMMENT '描述',
  `order_num` INT(11) DEFAULT NULL COMMENT '排序号',
  `shown` TINYINT(2) DEFAULT '1' COMMENT '是否展示',
  `level` INT(11) NOT NULL COMMENT '菜单层级,从0开始',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';

资源表

CREATE TABLE `resource` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '资源id',
  `name` VARCHAR(200) NOT NULL COMMENT '资源名称',
  `url` VARCHAR(200) NOT NULL COMMENT '资源url',
  `category_id` INT(11) DEFAULT NULL COMMENT '分类id',
  `description` VARCHAR(500) DEFAULT NULL COMMENT '简介',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`),
  KEY `idx_category_id` (`category_id`)
) ENGINE=INNODB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COMMENT='资源表';

资源分类表

CREATE TABLE `resource_category` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(200) DEFAULT NULL COMMENT '分类名称',
  `sort` INT(4) DEFAULT NULL COMMENT '排序',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='资源分类表';

用户角色中间表

CREATE TABLE `user_role_relation` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '关系id',
  `user_id` INT(11) NOT NULL COMMENT '用户id',
  `role_id` INT(11) NOT NULL COMMENT '角色id',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色关系表';

角色菜单中间表

CREATE TABLE `role_menu_relation` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `menu_id` INT(11) NOT NULL COMMENT '菜单id',
  `role_id` INT(11) NOT NULL COMMENT '角色id',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=302 DEFAULT CHARSET=utf8mb4 COMMENT='角色和菜单关系表';

角色资源中间表

CREATE TABLE `role_resource_relation` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '关系id',
  `resource_id` INT(11) NOT NULL COMMENT '资源id',
  `role_id` INT(11) NOT NULL COMMENT '角色id',
  `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
  `updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8mb4 COMMENT='角色和资源关系表';

权限管理 - 角色模块接口实现

角色列表查询 & 条件查询
需求分析

点击角色列表按钮进行角色列表展示

查看接口文档,进行编码
实体类 Role
public class Role {
    private Integer id;
    private String code;
    private String name;
    private String description;
    private Date createdTime;
    private Date updatedTime;
    private String createdBy;
    private String updatedBy;
    // getter setter toString ...
}
Dao 层 `RoleMapper`
public interface RoleMapper {
    List<Role> findAllRole(Role role);
}
<?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.renda.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`
public interface RoleService {
    List<Role> findAllRole(Role role);
}
@Service
public class RoleServiceImpl implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public List<Role> findAllRole(Role role) {
        return roleMapper.findAllRole(role);
    }
}
Web 层 `RoleController`
@RestController
@RequestMapping("/role")
public class RoleController {
    @Autowired
    private RoleService roleService;

    @RequestMapping("/findAllRole")
    public ResponseResult findAllRole(@RequestBody Role role) {
        List<Role> roleList = roleService.findAllRole(role);
        return new ResponseResult(true, 200, "查询所有角色成功", roleList);
    }
}
Postman 测试接口
添加 & 修改角色
需求分析

新建:点击提交按钮,将页面内容保存到数据库

修改:点击编辑按钮,由前端实现数据回显,在回显页面进行数据修改,将修改后值更新到数据库中

查看接口文档,进行编码
Dao 层 `RoleMapper`
void saveRole(Role role);

void updateRole(Role role);
<!-- 新增角色 -->
<insert id="saveRole" parameterType="Role">
    insert into roles (`name`, `code`, `description`, `created_time`, `updated_time`, `created_by`, `updated_by`)
    values (#{name}, #{code}, #{description}, #{createdTime}, #{updatedTime}, #{createdBy}, #{updatedBy})
</insert>

<!-- 更新角色 -->
<update id="updateRole" parameterType="Role">
    update roles
    <trim prefix="set" suffixOverrides=",">
        <if test="name != null and name != ''">
            `name` = #{name},
        </if>
        <if test="code != null and code != ''">
            `code` = #{code},
        </if>
        <if test="description != null and description != ''">
            `description` = #{description},
        </if>
        <if test=" updatedTime != null">
            `updated_time` = #{updatedTime},
        </if>
        <if test="updatedBy != null and updatedBy != ''">
            `updated_by` = #{updatedBy}
        </if>
    </trim>
    <where>
        <if test="id != null and id != ''">
            `id` = #{id}
        </if>
    </where>
</update>
Service 层 `RoleService`
void saveRole(Role role);

void updateRole(Role role);
@Override
public void saveRole(Role role) {
    // 封装数据
    Date date = new Date();
    role.setCreatedTime(date);
    role.setUpdatedTime(date);
    if (role.getCreatedBy() == null || "".equals(role.getCreatedBy())) {
        role.setCreatedBy("system");
        role.setUpdatedBy("system");
    }
    // 调用 mapper 方法
    roleMapper.saveRole(role);
}

@Override
public void updateRole(Role role) {
    // 封装数据
    role.setUpdatedTime(new Date());
    if (role.getUpdatedBy() == null || "".equals(role.getUpdatedBy())) {
        role.setUpdatedBy("system");
    }
    // 调用 mapper 方法
    roleMapper.updateRole(role);
}
Web 层 `RoleController`
@RequestMapping("/saveOrUpdateRole")
public ResponseResult saveOrUpdateRole(@RequestBody Role role) {
    if (role.getId() == null) {
        // 添加
        roleService.saveRole(role);
        return new ResponseResult(true, 200, "添加角色成功", null);
    } else {
        // 更新
        roleService.updateRole(role);
        return new ResponseResult(true, 200, "更新角色成功", null);
    }
}
Postman 测试接口
分配菜单
需求分析

点击分配菜单,回显可选择菜单信息,并回显选中状态;点击保存按钮为角色保存所选择的菜单信息

查询所有菜单列表
实体类 Menu
public class Menu {
    // 主键 id
    private Integer id;
    // 父菜单 id
    private int parentId;
    // 菜单路径
    private String href;
    // 菜单图标
    private String icon;
    // 菜单名称
    private String name;
    // 描述
    private String description;
    // 排序号
    private int orderNum;
    // 是否展示
    private int shown;
    // 菜单层级,从 0 开始
    private int level;
    // 创建时间
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createdTime;
    // 更新时间
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updatedTime;
    // 创建人
    private String createdBy;
    // 更新人
    private String updatedBy;
    // 声明集合:当前父级菜单所关联的子级菜单
    private List<Menu> subMenuList;
    // getter setter toString ...
}
Dao 层 `MenuMapper`
public interface MenuMapper {
    List<Menu> findSubMenuListByPid(int pid);
}
<?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.renda.dao.MenuMapper">
    <resultMap id="menuResult" type="Menu">
        <id column="id" property="id"/>
        <result column="href" property="href"/>
        <result column="icon" property="icon"/>
        <result column="name" property="name"/>
        <result column="parent_id" property="parentId"/>
        <result column="description" property="description"/>
        <result column="order_num" property="orderNum"/>
        <result column="shown" property="shown"/>
        <result column="level" property="level"/>
        <result column="created_time" property="createdTime"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="updated_by" property="updatedBy"/>
        <collection property="subMenuList" ofType="Menu" select="findSubMenuListByPid" column="id"/>
    </resultMap>

    <select id="findSubMenuListByPid" parameterType="int" resultMap="menuResult">
        select * from menu where parent_id = #{id}
    </select>
</mapper>
Service 层 `MenuService`
public interface MenuService {
    List<Menu> findSubMenuListByPid(int pid);
}
@Service
public class MenuServiceImpl implements MenuService {
    @Autowired
    private MenuMapper menuMapper;

    @Override
    public List<Menu> findSubMenuListByPid(int pid) {
        return menuMapper.findSubMenuListByPid(pid);
    }
}
Web 层 `RoleController`
@RequestMapping("/findAllMenu")
public ResponseResult findAllMenu() {
    // -1 表示查询所有的父级菜单以及关联的子级菜单
    List<Menu> menuList = menuService.findSubMenuListByPid(-1);
    // 响应数据
    HashMap<String, Object> map = new HashMap<>();
    map.put("parentMenuList", menuList);
    return new ResponseResult(true, 200, "查询所有的父子菜单信息成功", map);
}
Postman 测试接口
根据角色 ID 查询关联菜单 ID
Dao 层 `RoleMapper`
List<Integer> findMenuByRoleId(Integer roleId);
<select id="findMenuByRoleId" resultType="int" parameterType="int">
    SELECT
    m.`id`
    FROM roles r, role_menu_relation rm, menu m
    WHERE
    r.`id` = rm.`role_id` AND
    rm.`menu_id` = m.`id` AND
    r.`id` = #{roleId};
</select>
Service 层 `RoleService`
List<Integer> findMenuByRoleId(Integer roleId);
@Override
public List<Integer> findMenuByRoleId(Integer roleId) {
    return roleMapper.findMenuByRoleId(roleId);
}
Web 层 `RoleController`
@RequestMapping("/findMenuByRoleId")
public ResponseResult findMenuByRoleId(Integer roleId) {
    List<Integer> roleIdlist = roleService.findMenuByRoleId(roleId);
    return new ResponseResult(true, 200, "查询角色关联的菜单信息成功", roleIdlist);
}
Postman 测试接口
为角色分配菜单列表
`Role_menu_relation` 实体类
public class Role_menu_relation {
    private Integer id;
    private Integer menuId;
    private Integer roleId;
    private Date createdTime;
    private Date updatedTime;
    private String createdBy;
    private String updatedby;
    // getter setter toString ...
}
`RoleMenuVO`

View Object 表现层对象,主要用于表现层来接收参数的

public class RoleMenuVo {
    private Integer roleId;
    private List<Integer> menuIdList;
    // getter setter toString ...
}
Dao 层 `RoleMapper`
void deleteRoleContextMenu(Integer roleId);

void roleContextMenu(RoleMenuRelation roleMenuRelation);
<!-- 根据 roleid 删除在中间表与菜单的关联关系 -->
<delete id="deleteRoleContextMenu" parameterType="int">
    delete from role_menu_relation where role_id = #{roleId}
</delete>

<!-- 为角色分配菜单 -->
<insert id="roleContextMenu" parameterType="RoleMenuRelation">
    insert into role_menu_relation values (null,#{menuId},#{roleId},#{createdTime},#{updatedTime},#{createdBy},#{updatedby})
</insert>
Service 层 `RoleService`
void roleContextMenu(RoleMenuVo roleMenuVo);
@Override
public void roleContextMenu(RoleMenuVo roleMenuVo) {
    // 清空中间表的关联关系
    roleMapper.deleteRoleContextMenu(roleMenuVo.getRoleId());
    // 为角色分配菜单
    for (Integer mId : roleMenuVo.getMenuIdList()) {
        // 新建中间表数据
        RoleMenuRelation roleMenuRelation = new RoleMenuRelation();
        roleMenuRelation.setMenuId(mId);
        roleMenuRelation.setRoleId(roleMenuVo.getRoleId());
        // 封装数据
        Date date = new Date();
        roleMenuRelation.setCreatedTime(date);
        roleMenuRelation.setUpdatedTime(date);
        roleMenuRelation.setCreatedBy("system");
        roleMenuRelation.setUpdatedby("system");
        roleMapper.roleContextMenu(roleMenuRelation);
    }
}
Web 层 `RoleController`
@RequestMapping("/RoleContextMenu")
public ResponseResult roleContextMenu(@RequestBody RoleMenuVo roleMenuVo) {
    roleService.roleContextMenu(roleMenuVo);
    return new ResponseResult(true, 200, "为角色分配菜单成功", null);
}
Postman 测试接口
删除角色
需求分析

点击删除按钮,将选中的角色信息删除

查看接口文档,进行编码
Dao 层 `RoleMapper`
void deleteRole(Integer roleId);
<delete id="deleteRole" parameterType="int">
    delete from roles where id = #{roleId}
</delete>
Service 层 `RoleService`
void deleteRole(Integer roleId);
@Override
public void deleteRole(Integer roleId) {
    // 清空中间表的关联关系
    roleMapper.deleteRoleContextMenu(roleId);
    // 删除角色
    roleMapper.deleteRole(roleId);
}
Web 层 `RoleController`
@RequestMapping("/deleteRole")
public ResponseResult deleteRole(Integer id) {
    roleService.deleteRole(id);
    return new ResponseResult(true, 200, "删除角色成功", null);
}
Postman 测试接口

权限管理 - 菜单模块接口实现

菜单列表查询
需求分析

点击菜单列表,对菜单信息进行列表展示

查看接口文档,进行编码
Dao 层 `MenuMapper`
List<Menu> findAllMenu();
<select id="findAllMenu" resultType="Menu">
    select * from menu
</select>
Service 层 `MenuService`
List<Menu> findAllMenu();
@Override
public List<Menu> findAllMenu() {
    return menuMapper.findAllMenu();
}
Web 层 `MenuController`
@RequestMapping("/findAllMenu")
public ResponseResult findAllMenu() {
    List<Menu> menuList = menuService.findAllMenu();
    return new ResponseResult(true, 200, "查询所有菜单信息成功", menuList);
}
Postman 测试接口
查询菜单信息(回显)
需求分析

点击添加菜单按钮,跳转到添加菜单页面,回显当前添加菜单可以选择的上级菜单信息

查看接口文档,进行编码
Dao 层 `MenuMapper`
Menu findMenuById(Integer id);
<select id="findMenuById" parameterType="int" resultType="Menu">
    select * from menu where id = #{id}
</select>
Service 层 `MenuService`
Menu findMenuById(Integer id);
@Override
public Menu findMenuById(Integer id) {
    return menuMapper.findMenuById(id);
}
Web 层 `MenuController`
@RequestMapping("/findMenuInfoById")
public ResponseResult findMenuInfoById(Integer id) {
    // 根据 id 的值是否为 -1 判断当前是更新还是添加操作
    if (id == -1) {
        // 添加操作,回显信息中只需要查询所有父级菜单信息
        List<Menu> parentMenuList = menuService.findSubMenuListByPid(-1);
        // 封装数据
        HashMap<String, Object> map = new HashMap<>();
        map.put("menuInfo", null);
        map.put("parentMenuList", parentMenuList);
        return new ResponseResult(true, 200, "添加回显成功", map);
    } else {
        // 修改操作,回显信息需要包含所有父级菜单信息和菜单本身的信息
        List<Menu> parentMenuList = menuService.findSubMenuListByPid(-1);
        Menu menuInfo = menuService.findMenuById(id);
        // 封装数据
        HashMap<String, Object> map = new HashMap<>();
        map.put("menuInfo", menuInfo);
        map.put("parentMenuList", parentMenuList);
        return new ResponseResult(true, 200, "修改回显成功", map);
    }
}
Postman 测试接口
添加 & 修改菜单
需求分析

添加:点击提交按钮,将数据提交到数据库

修改:页面回显基础上,点击提交按钮真正进行数据修改

查看接口文档,进行编码
Dao 层 `MenuMapper`
void saveMenu(Menu menu);

void updateMenu(Menu menu);
<!-- 新增菜单 -->
<insert id="saveMenu" parameterType="Menu">
    insert into menu (`parent_id`,`href`,`icon`,`name`,`description`,`order_num`,`shown`,`level`,
    `created_time`,`updated_time`,`created_by`,`updated_by`)
    values (#{parentId},#{href},#{icon},#{name},#{description},#{orderNum},#{shown},#{level},
    #{createdTime},#{updatedTime},#{createdBy},#{updatedBy})
</insert>

<!-- 更新菜单 -->
<update id="updateMenu" parameterType="Menu">
    update menu
    <trim prefix="set" suffixOverrides=",">
        <if test="`parentId` != null and `parentId` != ''">
            `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` != null and `orderNum` != ''">
            `order_num` = #{orderNum},
        </if>
        <if test="`shown` != null and `shown` != ''">
            `shown` = #{shown},
        </if>
        <if test="`level` != null and `level` != ''">
            `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 and id != ''">
            `id` = #{id}
        </if>
    </where>
</update>
Service 层 `MenuService`
void saveMenu(Menu menu);

void updateMenu(Menu menu);
@Override
public void saveMenu(Menu menu) {
    // 封装数据
    Date date = new Date();
    menu.setCreatedTime(date);
    menu.setUpdatedTime(date);
    if (menu.getCreatedBy() == null || "".equals(menu.getCreatedBy())) {
        menu.setCreatedBy("system");
        menu.setUpdatedBy("system");
    }
    // 调用 mapper 方法
    menuMapper.saveMenu(menu);
}

@Override
public void updateMenu(Menu menu) {
    // 封装数据
    menu.setUpdatedTime(new Date());
    if (menu.getUpdatedBy() == null || "".equals(menu.getUpdatedBy())) {
        menu.setUpdatedBy("system");
    }
    // 调用 mapper 方法
    menuMapper.updateMenu(menu);
}
Web 层 `MenuController`
@RequestMapping("/saveOrUpdateMenu")
public ResponseResult saveOrUpdateMenu(@RequestBody Menu menu) {
    if (menu.getId() == null) {
        // 新增
        menuService.saveMenu(menu);
        return new ResponseResult(true, 200, "添加菜单成功", null);
    } else {
        // 修改
        menuService.updateMenu(menu);
        return new ResponseResult(true, 200, "修改菜单成功", null);
    }
}
Postman 测试接口

权限管理 - 资源模块接口实现

资源分类信息查询
需求分析

查询资源分类信息列表

查看接口文档,进行编码
实体类 `ResourceCategory`
public class ResourceCategory {
    private Integer id;
    private String name;
    private Integer sort;
    private Date createdTime;
    private Date updatedTime;
    private String createdBy;
    private String updatedBy;
    // setter getter toString ...
}
Dao 层 `ResourceCategoryMapper`
public interface ResourceCategoryMapper {
    List<ResourceCategory> findAllResourceCategory();
}
<?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.renda.dao.ResourceCategoryMapper">
    <select id="findAllResourceCategory" resultType="ResourceCategory">
        select * from resource_category
    </select>
</mapper>
Service 层 `ResourceCategoryService`
public interface ResourceCategoryService {
    List<ResourceCategory> findAllResourceCategory();
}
@Service
public class ResourceCategoryServiceImpl implements ResourceCategoryService {
    @Autowired
    private ResourceCategoryMapper resourceCategoryMapper;

    @Override
    public List<ResourceCategory> findAllResourceCategory() {

        return resourceCategoryMapper.findAllResourceCategory();
    }
}
Web 层 `ResourceCategoryController`
@RestController
@RequestMapping("/ResourceCategory")
public class ResourceCategoryController {
    @Autowired
    private ResourceCategoryService resourceCategoryService;

    @RequestMapping("/findAllResourceCategory")
    public ResponseResult findAllResourceCategory() {
        List<ResourceCategory> allResourceCategory = resourceCategoryService.findAllResourceCategory();
        return new ResponseResult(true, 200, "查询所有分类信息成功", allResourceCategory);
    }
}
Postman 测试接口
资源分页 & 多条件查询
需求分析

资源列表及多条件组合查询

查看接口文档,进行编码
实体类 `Resource`
public class Resource {
    private Integer id;
    private String name;
    private String url;
    private Integer categoryId;
    private String description;
    private Date createdTime;
    private Date updatedTime;
    private String createdBy;
    private String updatedBy;
    // setter getter toString ...
}
`ResourceVO`

View Object 表现层对象,主要用于表现层来接收参数的

public class ResourseVo {
    private Integer currentPage;
    private Integer pageSize;
    private String name;
    private Integer categoryId;
    private String url;
    // setter getter toString ...
}
Dao 层 `ResourceMapper`
public interface ResourceMapper {
    List<Resource> findAllResourceByPage(ResourseVo resourseVo);
}
<?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.renda.dao.ResourceMapper">
    <select id="findAllResourceByPage" parameterType="ResourseVo" resultType="Resource">
        select * from resource
        <where>
            <if test="name != null and name != ''">
                and `name` like concat('%', #{name}, '%')
            </if>
            <if test="url != null and url != ''">
                and `url` = #{url}
            </if>
            <if test="categoryId != null and categoryId != ''">
                and `category_id` = #{categoryId}
            </if>
        </where>
    </select>
</mapper>
Service 层 `ResourceService`
public interface ResourceService {
    PageInfo<Resource> findAllResourceByPage(ResourseVo resourseVo);
}
@Service
public class ResourceServiceImpl implements ResourceService {
    @Autowired
    private ResourceMapper resourceMapper;

    @Override
    public PageInfo<Resource> findAllResourceByPage(ResourseVo resourseVo) {
        // 分页查询
        PageHelper.startPage(resourseVo.getCurrentPage(), resourseVo.getPageSize());
        List<Resource> resourceList = resourceMapper.findAllResourceByPage(resourseVo);
        return new PageInfo<>(resourceList);
    }
}
Web 层 `ResourceController`
@RestController
@RequestMapping("/resource")
public class ResourceController {
    @Autowired
    private ResourceService resourceService;

    @RequestMapping("/findAllResource")
    public ResponseResult findAllResource(@RequestBody ResourseVo resourseVo) {
        PageInfo<Resource> resourceByPage = resourceService.findAllResourceByPage(resourseVo);
        return new ResponseResult(true, 200, "资源信息分页多条件查询成功", resourceByPage);
    }
}
Postman 测试接口
添加 & 更新资源信息
需求分析

新建:点击提交按钮,将页面内容保存到数据库

修改:点击编辑按钮,由前端实现数据回显,在回显页面进行数据修改,将修改后值更新到数据库中

查看接口文档,进行编码
Dao 层 `ResourceMapper`
void saveResource(Resource resource);

void updateResource(Resource resource);
<!-- 添加资源信息 -->
<insert id="saveResource" parameterType="Resource">
    insert into resource values (null,#{name},#{url},#{categoryId},#{description},#{createdTime},
    #{updatedTime},#{createdBy},#{updatedBy});
</insert>

<!-- 更新资源信息 -->
<update id="updateResource" parameterType="Resource">
    update resource
    <trim prefix="set" suffixOverrides=",">
        <if test="name != null and name != ''">
            `name` = #{name},
        </if>
        <if test="url != null and url != ''">
            `url` = #{url},
        </if>
        <if test="categoryId != null and categoryId != ''">
            `category_id` = #{categoryId},
        </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` = #{id}
        </if>
    </where>
</update>
Service 层 `ResourceService`
void saveResource(Resource resource);

void updateResource(Resource resource);
@Override
public void saveResource(Resource resource) {
    // 封装数据
    Date date = new Date();
    resource.setCreatedTime(date);
    resource.setUpdatedTime(date);
    if (resource.getCreatedBy() == null || "".equals(resource.getCreatedBy())) {
        resource.setCreatedBy("system");
        resource.setUpdatedBy("system");
    }
    // 调用 mapper 方法
    resourceMapper.saveResource(resource);
}

@Override
public void updateResource(Resource resource) {
    // 封装数据
    resource.setUpdatedTime(new Date());
    if (resource.getUpdatedBy() == null || "".equals(resource.getUpdatedBy())) {
        resource.setUpdatedBy("system");
    }
    // 调用 mapper 方法
    resourceMapper.updateResource(resource);
}
Web 层 `ResourceController`
@RequestMapping("/saveOrUpdateResource")
public ResponseResult saveOrUpdateResource(@RequestBody Resource resource) {
    if (resource.getId() == null) {
        // 添加
        resourceService.saveResource(resource);
        return new ResponseResult(true, 200, "添加资源信息成功", null);
    } else {
        // 更新
        resourceService.updateResource(resource);
        return new ResponseResult(true, 200, "更新资源信息成功", null);
    }
}
Postman 测试接口
删除资源信息
需求分析

点击删除按钮,将对应的资源信息删除

查看接口文档,进行编码
Dao 层 `ResourceMapper`
void deleteResource(Integer resourceId);
<delete id="deleteResource" parameterType="int">
    delete from resource where id = #{resourceId}
</delete>
Service 层 `ResourceService`
void deleteResource(Integer resourceId);
@Override
public void deleteResource(Integer resourceId) {
    // 清空中间表的关联关系
    resourceMapper.deleteRoleResourceRelation(resourceId);
    // 删除资源
    resourceMapper.deleteResource(resourceId);
}
Web 层 `ResourceController`
@RequestMapping("/deleteResource")
public ResponseResult deleteResource(Integer id) {
    resourceService.deleteResource(id);
    return new ResponseResult(true, 200, "删除资源成功", null);
}
Postman 测试接口

登陆及动态菜单展示

加密算法 MD5 介绍
什么是 MD5

MD5 加密全程是 Message-Digest Algorithm 5(信息 - 摘要算法),它对信息进行摘要采集,再通过一定的位运算,最终获取加密后的 MD5 字符串。

MD5 有哪些特点

MD5 加密的特点主要有以下几点:

针对不同长度待加密的数据、字符串等等,其都可以返回一个固定长度的 MD5 加密字符串。(通常 32 位的 16 进制字符串)

其加密过程几乎不可逆,除非维护一个庞大的 Key - Value 数据库来进行碰撞破解,否则几乎无法解开。

运算简便,且可实现方式多样,通过一定的处理方式也可以避免碰撞算法的破解(加盐:随机字符串)。

对于一个固定的字符串。数字等等,MD5 加密后的字符串是固定的,也就是说不管 MD5 加密多少次,都是同样的结果。

登陆
需求分析

输入用户名密码,点击登陆按钮,进行用户登陆

使用 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>
添加工具类
public class Md5 {

    public final static String md5key = "renda";

    /**
     * MD5 方法
     * @param text 明文
     * @param key 密钥
     * @return 密文
     */
    public static String md5(String text, String key) {
        // 加密后的字符串
        String encodedStr = DigestUtils.md5Hex(text + key);
        System.out.println("MD5 加密后的字符串为:encodedStr = " + encodedStr);
        return encodedStr;
    }

    /**
     * MD5 验证方法
     * @param text 明文
     * @param key 密钥
     * @param md5 密文
     * @return true/false
     */
    public static boolean verify(String text, String key, String md5) {
        // 根据传入的密钥进行验证
        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 {
        // 添加用户的时候,要将明文密码转换成密文密码
        System.out.println(Md5.md5("123456", "renda"));
        // 调用 verify 方法进行密码的校验
        System.out.println(Md5.verify("123456", "renda", "58c40a69ae7e7c92bced90ed1e974624"));
    }

}
查看接口文档,进行编码
Dao 层 `UserMapper`
User login(User user);
<select id="login" parameterType="User" resultType="User">
    select * from `user` where `phone` = #{phone}
</select>
Service 层 `UserService`
User login(User user);
@Override
public User login(User user) {
    // 调用 mapper 方法,传递 user 对象
    User userFromDatabase = userMapper.login(user);
    if (userFromDatabase != null && Md5.verify(user.getPassword(), "renda", userFromDatabase.getPassword())) {
        return userFromDatabase;
    } else {
        return null;
    }
}
Web 层 `UserController`
@RequestMapping("/login")
public ResponseResult login(User user, HttpServletRequest request) {
    User userFromDatebase = userService.login(user);
    if (userFromDatebase != null) {
        // 保存用户 id 及 access_token 到 session 中
        HttpSession requestSession = request.getSession();
        String accessToken = UUID.randomUUID().toString();
        System.out.println(accessToken);
        requestSession.setAttribute("access_token", accessToken);
        requestSession.setAttribute("user_id", userFromDatebase.getId());

        // 将查询出来的信息响应给前台
        HashMap<String, Object> map = new HashMap<>();
        map.put("access_token", accessToken);
        map.put("user_id", userFromDatebase.getId());

        return new ResponseResult(true, 200, "登陆成功", map);
    } else {
        return new ResponseResult(true, 400, "用户名密码错误", null);
    }
}
Postman 测试接口
分配角色(回显)
需求分析

点击分配角色,将该用户所具有的角色信息进行回显

查看接口文档,进行编码
Dao 层 `UserMapper`
List<Role> findUserRelationRoleById(Integer id);
<select id="findUserRelationRoleById" parameterType="int" resultType="Role">
    SELECT * FROM
    roles r INNER JOIN user_role_relation ur ON r.`id` = ur.`role_id`
    WHERE ur.`user_id` = #{id};
</select>
Service 层 `UserService`
List<Role> findUserRelationRoleById(Integer id);
@Override
public List<Role> findUserRelationRoleById(Integer id) {
    return userMapper.findUserRelationRoleById(id);
}
Web 层 `UserController`
@RequestMapping("/findUserRoleById")
public ResponseResult findUserRelationRoleById(Integer id) {
    List<Role> roleList = userService.findUserRelationRoleById(id);
    return new ResponseResult(true, 200, "分配角色回显成功", roleList);
}
Postman 测试接口
分配角色
需求分析

点击确定按钮,真正实现用户角色关联

查看接口文档,进行编码
`UserVo`
public class UserVo {
    private List<Integer> roleIdList;
    private Integer userId;
    ...
    // getter setter ...
}
Dao 层 `UserMapper`
void deleteUserContextRole(Integer userId);

void userContextRole(UserRoleRelation userRoleRelation);
<!-- 根据用户 ID 清空中间表 -->
<delete id="deleteUserContextRole" parameterType="int">
    delete from user_role_relation where user_id = #{userId}
</delete>

<!-- 分配角色:用户关联角色 -->
<insert id="userContextRole" parameterType="UserRoleRelation">
    insert into user_role_relation values (null,#{userId},#{roleId},#{createdTime},#{updatedTime},#{createdBy},#{updatedBy})
</insert>
Service 层 `UserService`
void userContextRole(UserVo userVo);
@Override
public void userContextRole(UserVo userVo) {
    // 根据用户 ID 清空中间表关联关系
    userMapper.deleteUserContextRole(userVo.getUserId());
    // 建立关联关系
    for (Integer roleId : userVo.getRoleIdList()) {
        // 新建 UserRoleRelation
        UserRoleRelation userRoleRelation = new UserRoleRelation();
        userRoleRelation.setUserId(userVo.getUserId());
        userRoleRelation.setRoleId(roleId);
        // 封装数据
        Date date = new Date();
        userRoleRelation.setCreatedTime(date);
        userRoleRelation.setUpdatedTime(date);
        userRoleRelation.setCreatedBy("system");
        userRoleRelation.setUpdatedBy("system");
        userMapper.userContextRole(userRoleRelation);
    }
}
Web 层 `UserController`
@RequestMapping("/userContextRole")
public ResponseResult userContextRole(@RequestBody UserVo userVo) {
    userService.userContextRole(userVo);
    return new ResponseResult(true, 200, "分配角色成功", null);
}
Postman 测试接口
动态菜单显示
需求分析

登陆成功后,根据用户所拥有的权限信息,进行菜单列表动态展示

查看接口文档,进行编码
Dao 层 `UserMapper`
List<Role> findUserRelationRoleById(Integer userId);
List<Menu> findParentMenuByRoleId(List<Integer> roleIdList);
List<Menu> findSubMenuByParentId(Integer parentId);
List<Resource> findResourceByRoleId(List<Integer> roleIdList);
<!-- 根据用户 ID 查询关联的角色信息 -->
<select id="findUserRelationRoleById" parameterType="int" resultType="Role">
    SELECT * FROM
    roles r INNER JOIN user_role_relation ur ON r.`id` = ur.`role_id`
    WHERE ur.`user_id` = #{userId};
</select>

<!-- 根据角色 ID,查询角色所拥有的顶级菜单 -->
<select id="findParentMenuByRoleId" parameterType="List" resultType="Menu">
    SELECT
    DISTINCT m.*
    FROM
    roles r INNER JOIN role_menu_relation rm ON r.`id` = rm.`role_id`
    INNER JOIN menu m ON m.`id` = rm.`menu_id`
    WHERE
    m.`parent_id` = -1 AND r.`id` IN
    <foreach collection="list" item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

<!-- 根据 parentId,查询子菜单信息 -->
<select id="findSubMenuByParentId" parameterType="int" resultType="Menu">
    SELECT * FROM `menu` WHERE parent_id = #{parentId}
</select>

<!-- 根据角色 ID,获取用户拥有的资源权限信息 -->
<select id="findResourceByRoleId" parameterType="List" resultType="Resource">
    SELECT DISTINCT r.*
    FROM
    resource r INNER JOIN role_resource_relation rr ON r.`id` = rr.`resource_id`
    INNER JOIN roles ro ON ro.`id` = rr.`role_id`
    WHERE ro.`id` IN
    <foreach collection="list" item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>
Service 层 `UserService`
ResponseResult getUserPermissions(Integer userId);
@Override
public ResponseResult getUserPermissions(Integer userId) {
    // 获取当前用户拥有的角色
    List<Role> roleList = userMapper.findUserRelationRoleById(userId);
    if (roleList == null || roleList.isEmpty()) {
        return new ResponseResult(true, 200, "用户权限信息为空", null);
    }
    // 获取角色 ID,保存到 List 集合中
    ArrayList<Integer> roleIds = new ArrayList<>();
    for (Role role : roleList) {
        roleIds.add(role.getId());
    }

    // 根据角色 ID 查询父菜单
    List<Menu> parentMenuList = userMapper.findParentMenuByRoleId(roleIds);

    // 查询并封装父菜单关联的子菜单
    for (Menu menu : parentMenuList) {
        menu.setSubMenuList(userMapper.findSubMenuByParentId(menu.getId()));
    }

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

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

    return new ResponseResult(true, 200, "获取用户权限信息成功", map);
}
Web 层 `UserController`
@RequestMapping("/getUserPermissions")
public ResponseResult getUserPermissions(HttpServletRequest request) {
    // 获取请求头中的 token
    String accessTokenFromHeader = request.getHeader("Authorization");
    // 获取 session 中 token
    String accessToken = (String) request.getSession().getAttribute("access_token");
    // 判断 accessToken 是否一致
    if (accessTokenFromHeader.equals(accessToken)) {
        // 获取用户 id
        Integer userId = (Integer) request.getSession().getAttribute("user_id");
        // 调用 service,进行菜单信息查询
        return userService.getUserPermissions(userId);
    } else {
        return new ResponseResult(false, 400, "获取菜单信息失败", null);
    }
}
Postman 测试接口

先进行登录接口测试,获取 access_token

然后请求的 Headers 中增加一个 key 为 Authorization,Value 为登录获取的 access_token 的键值对。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Renda 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 权限概念介绍
    • 实现权限系统的原因
      • 权限控制基本原理
        • 权限系统经典五张表
    • 权限模块功能分析
      • 权限模块管理
        • 数据库表
        • 表关系介绍
    • 权限模块表设计
    • 权限管理 - 角色模块接口实现
      • 角色列表查询 & 条件查询
        • 需求分析
        • 查看接口文档,进行编码
      • 添加 & 修改角色
        • 需求分析
        • 查看接口文档,进行编码
      • 分配菜单
        • 需求分析
        • 查询所有菜单列表
        • 根据角色 ID 查询关联菜单 ID
        • 为角色分配菜单列表
      • 删除角色
        • 需求分析
        • 查看接口文档,进行编码
    • 权限管理 - 菜单模块接口实现
      • 菜单列表查询
        • 需求分析
        • 查看接口文档,进行编码
      • 查询菜单信息(回显)
        • 需求分析
        • 查看接口文档,进行编码
      • 添加 & 修改菜单
        • 需求分析
        • 查看接口文档,进行编码
    • 权限管理 - 资源模块接口实现
      • 资源分类信息查询
        • 需求分析
        • 查看接口文档,进行编码
      • 资源分页 & 多条件查询
        • 需求分析
        • 查看接口文档,进行编码
      • 添加 & 更新资源信息
        • 需求分析
        • 查看接口文档,进行编码
      • 删除资源信息
        • 需求分析
        • 查看接口文档,进行编码
    • 登陆及动态菜单展示
      • 加密算法 MD5 介绍
        • 什么是 MD5
        • MD5 有哪些特点
      • 登陆
        • 需求分析
        • 使用 MD5
        • 查看接口文档,进行编码
      • 分配角色(回显)
        • 需求分析
        • 查看接口文档,进行编码
      • 分配角色
        • 需求分析
        • 查看接口文档,进行编码
      • 动态菜单显示
        • 需求分析
        • 查看接口文档,进行编码
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档