BootStrap的官方文档:https://www.bootcss.com/
RBAC权限管理系统
目标:
了解权限管理在系统中的作用
熟练使用SSM搭建项目开发环境
熟练使用jQuery完成前台页面的基本功能
完成RBAC权限管理系统的开发
通过该系统熟悉WEB应用的基本开发流程
权限管理系统
基于角色的访问控制,这种模型的基本概念是把许可权(Permission)与角色(Role)联系在一起,用户通过充当合适角色的成员而获得该角色的许可权。
在权限管理中使用最多的还是基于角色访问控制(RBAC: Role Based Access Control)
基于角色的访问控制(RBAC):角色决定访问权限。用组织角色来同意或拒绝访问
RBAC重要对象
用户(User):角色施加的主体;用户通过拥有某个或多个角色以得到对应的权限。
角色(Role):表示一组权限的集合。
权限(Permission):用于限定能够访问的一个资源。
思路:
需提供操作来为用户分配角色和权限(用户、角色和权限数据还有其之间关系数据都得保存起来)。
获取访问资源的用户(登录)和被访问的资源
若用户是管理员,直接放行访问;
若资源不需要权限控制,直接放行访问;
其他情况则从数据库中查询该用户(先找角色,再找角色对应的权限)是否具有访问该资源的权限,若没有,提醒用户权限不足,若有放行访问。
环境搭建
数据库表结构理解, 部门表,员工表,角色表,权限表
创建Maven项目, 项目添加依赖, 以及相关配置文件
重命名包,删除一些之前引入的样式文件与JS 文件
创建表,使用逆向工程根据表创建出基本的项目结构,拷贝项目需要的资源到项目中:

实现分页功能:
查询的类:用户可设置的参数
//封装分页查询对象@Getter@Setterpublic class QueryObject {private int currentPage = 1; //当前页private int pageSize = 3; //每页显示条数//计算开始的索引public int getStart(){return (currentPage - 1) * pageSize;}}
分页结果对象:包含页面中的7个分页数据
//封装页面中需要的数据(分页结果对象)@Getterpublic class PageResult {private Integer currentPage; //当前页private Integer pageSize; //每页显示条数//sql查询出来的数据private List<?> list; //结果集private Integer totalCount; //结果总数//计算出来的数据private Integer prevPage; //上一页private Integer nextPage; //下一页private Integer totalPage; //总页数//当总条数为0时使用public PageResult(Integer pageSize) {this(1, 0, Collections.EMPTY_LIST, pageSize);}public PageResult(Integer currentPage, Integer totalCount, List<?> list,Integer pageSize) {this.currentPage = currentPage;this.pageSize = pageSize;this.list = list;this.totalCount = totalCount;//计算this.totalPage = totalCount % pageSize == 0 ? totalCount pageSize :totalCount pageSize + 1;this.prevPage = currentPage > 1 ? currentPage - 1 : 1;this.nextPage = currentPage < totalPage ? currentPage + 1 : totalPage;}}
在部门的 Mapper 中增加分页查询的两个方法及对应XML 元素
<!-- 查询总条数 --><select id="selectForCount" resultType="java.lang.Integer">SELECT COUNT(*) FROM department</select><!-- 查询分页结果集 --><select id="selectForList" resultType="cn.wolfcode.rbac.domain.Department">SELECT id,name,sn FROM department LIMIT #{start},#{pageSize}</select>
在service中添加分页查询的方法:
/*** 分页查询** @param qo 封装查询时需要的数据#{xxx}来取其属性值* @return*/@Overridepublic PageResult query(QueryObject qo) {int count = departmentMapper.selectForCount(qo);//高查询时需要参数//判断是否有数据if (count == 0) {return new PageResult(qo.getPageSize());}//查询结果集List<Department> list = departmentMapper.selectForList(qo);return new PageResult(qo.getCurrentPage(), count, list, qo.getPageSize());}
controller调用分页方法:
// 提供一个方法处理查询所有部门数据请求,响应 HTML@RequestMapping("/list")public String list(Model model , @ModelAttribute("qo") QueryObject qo){//调用service的方法的到结果集PageResult result = departmentService.query(qo);//共享结果集:在页面遍历显示model.addAttribute("result", result);return "department/list"; //返回视图名称:加上前缀和后缀的jsp}
分析分页插件的使用:已经准备好,无需再写:

以前的分页代码非常麻烦,现在就比较简洁:

员工管理
分页查询与部门的分页查询基本相同,
创建controller,service,mapper分页相关方法,
员工的domain中添加一个部门对象,员工与部门之间是多对一的关系。
private Department dept;//关联属性
SQL的多表联查:
<!--员工多表联查--><select id="selectForList" resultMap="BaseResultMap">SELECT e.id, e.name, e.password, e.email, e.age, e.admin, d.id d_id, d.name d_nameFROM employee e LEFT JOIN department dON e.dept_id = d.idLIMIT #{start} , #{pageSize}</select>
将查询结果封装到resultMap中:

高级查询:关键字和部门的下拉框查询
除了封装分页数据,还要封装用户给的关键字信息,同时还要分页显示,所以继承QueryObject
@Getter@Setterpublic class EmployeeQuery extends QueryObject {//解决keyword属性不存在的问题 :自己的QOprivate String keyword;//显示部门信息:通过deptId 来显示private Long deptId = -1L;//默认值-1,因为jsp页面 -1 表示全部//覆写get方法,在里面排除keyword为空的情况public String getKeyword() {return StringUtils.hasLength(keyword) ? keyword.trim() : null;}}
controller器类:
@Controller@RequestMapping("/employee")public class EmployeeController {@Autowiredprivate IEmployeeService employeeService;@Autowiredprivate IDepartmentService departmentService;// 显示列表@RequestMapping("/list")public String list(Model model, @ModelAttribute("qo") EmployeeQuery qo) {PageResult result = employeeService.query(qo);model.addAttribute("result", result);//部门下拉框:显示部门的信息-->查到部门共享出去model.addAttribute("depts", departmentService.listAll());return "employee/list"; //跳转到员工的视图}}
在查询中条加了条件:

部门下拉框的显示和数据回显:

员工的添加和编辑:
controller中的input和saveOrUpdate方法
@RequestMapping("/input")public String input(Long id, Model model) {if (id != null) { // 编辑Employee employee = employeeService.get(id);//查询该对象然后共享model.addAttribute("employee", employee);//编辑状态下的部门回显: 在input界面或取遍历显示model.addAttribute("depts",departmentService.listAll());}return "employee/input";}@RequestMapping("/saveOrUpdate")public String saveOrUpdate(Employee employee) {//通过 id 判断是添加还是修改if (employee.getId() == null) {//添加employeeService.save(employee);} else {//编辑employeeService.update(employee);}return "redirect:/employee/list.do";}
编辑时input界面的回显问题:
编辑员工时,不显示密码操作相关组件:
<%-- 新增的时候才显示密码,编辑不显示密码:根据共享的员工是否为空来判断是添加还是编辑 --%><c:if test="${empty employee}"><%-- 密码显示相关的标签 --%></c:if>
角色管理:
1.创建角色表, 员工角色关系管理中间表

2.实现角色页面的crud功能:逆向工程、添加分页查询的方法
3.添加service和impl,拷贝部门的 Service 接口及实现类修改。
4.编写 RoleController 和拷贝员工 list.jsp 修改,拷贝部门的 Controller 及 jsp 修改。
编辑员工时,角色列表的移动
在EmployeeController中查询到角色数据,共享到页面
编辑员工时,角色列表的左边框的选项的显示以供选择。

在input.jsp页面中回显角色数据

在input.jsp页面中使用JS完成角色的左移有移功能
//左移动和右移function moveSelected(src, taget) {//把源中被选中的option,全部追加到目标元素中$("." + taget).append($("." + src + "> option:selected"));}//全部移动function moveAll(src, taget) {$("." + taget).append($("." + src + "> option"));}
超管隐藏角色编辑
选中超级管理员复选框,超级管理员不需要显示角色的编辑下拉框。
若是超级管理员,若打勾,则隐藏或者删除角色配置的页面元素;若不是打勾,则显示或者还原角色配置的页面元素。

ps:当需要拿到某一个元素再做操作的时候,需要先把页面加载完成才能拿到。如对按钮组件监听绑定事件。
//加载完整个页面:类似于window.onload=function(){}$(function () {}
页面代码的实现:
//员工input页面的超级管理员的点击显示处理var roleDiv;//提成一个全局变量//监听超管复选框的点击事件$("#admin").click(function () {//拿到事件源,获得选中的状态//$(this).val() 拿到的是on,这个值要用.prop("checked")来拿(复选框获取选中状态的方法)var val = $(this).prop("checked");//判断是否被选中if (val) {//被选中,就将角色的选择框删除roleDiv = $("#role").detach();//不删除事件和附加数据,因为数据还需要恢复} else {//没有选中:将数据恢复到角色框中-->作为兄弟跟在adminDiv后面$("#adminDiv").after(roleDiv);}});
员工保存时全选角色下拉框
下拉框中选中的数据的才会提交,若没有选中的数据是不会提交的,但是其实我们是想不选中的那些也要提交的。

思路:修改按钮为普通按钮,绑定点击事件处理函数,在函数中把右边的 select 元素中的 option 设置为选中的,再提交表单。

// 保存时:解决右边数据提交只会提交被选中的id:改成手动提交,且提交之前将所有的数据都为选中状态$("#submitBtn").click(function () {//把右边的下拉框的option全部选择$(".selfRoles option").prop("selected", true);//提交表单$("#editForm").submit();});
后台员工保存时处理角色
保存员工时:除向员工表中保存数据,还要处理与部门的关系数据,还需保存该角色的权限,即往 role_permission 表中存数据
修改 EmployeeController
@RequestMapping("/saveOrUpdate")public String saveOrUpdate(Employee employee,Long[] ids) {//通过 id 判断是添加还是修改if (employee.getId() == null) {//添加employeeService.save(employee, ids);} else {//编辑employeeService.update(employee,ids);}return "redirect:/employee/list.do";}修改员工 Service 接口及实现类
void save(Employee employee, Long[] ids);//保存员工是需要保存和角色的关系:通过角色的id来对应部门@Overridepublic void save(Employee employee, Long[] ids) {employeeMapper.insert(employee);//插入员工,再插入关系//判断传过来的 ids 数组是否为空if (ids != null && ids.length > 0) {//不为空时,保存时就要连关系一起保存for (Long roleId : ids) {employeeMapper.insertRelation(employee.getId(), roleId);}}}修改员工 Mapper 接口及 XML
部门回显
回显部门:要先查到,共享,再在页面中拿到数据。额外SQL或连表查询,后者效率高。
EmployeeMapper.xml


input.jsp

角色回显

还是先查出来,共享再回显。后台查询员工时也把该员工的角色查询出来
修改员工实体类,新增 roles 属性
private List<Role> roles = new ArrayList<>();//角色属性:一个员工可能有多个角色修改 EmployeeMapper.xml 中的 BaseResultMap,使用额外 SQL 把员工角色查询出来。



页面回显角色

角色去重
刚上面角色回显的时候,发现左右两边的角色有重复,应该是有右边有的角色,不应该在左边出现。
简单直接的思路:将右边存在的角色id存在数组中,判断左边的角色id在数组中是否存在,存在就将它删除。
//编辑时角色页面的两边数据去重//将右边的数据放到数组中,再判断左边的数据在数组中是否存在,存在则删除var selfIds = [];$('.selfRoles > option').each(function (index,ele) {//遍历右边获得的元素集selfIds.push($(ele).val());//拿到元素的id存到数组中});$('.allRoles > option').each(function (index,ele) {//遍历左边获得的元素集var id = $(ele).val();//拿到左边idif($.inArray(id,selfIds) > -1){//右边的id在数组中已经存在$(ele).remove();//删除已存在}});
另一种思路:页面加载完,拿左右两边的 option 对比,遍历左边每个,若不存右边,则保留,若存在则删除。

超管隐藏角色编辑
如果当前用户是超级管理员,它本身就具有全部角色,所以不需要显示角色相关的编辑界面。
思路:在页面接在完成的时候,就去判断当前员工是不是超管,是就隐藏。
$(function () {//超级管理员隐藏角色的编辑:页面加载完,若是超管,隐藏角色分配框//方式1:var checked = $('#admin').prop('checked');if (checked){console.log($('#role').detach());}//方式2:<c:if test="${employee.admin}">roleDiv = $('#role').detach();</c:if>}
员工编辑时处理角色 :更新关系
除更新员工表中此数据,还要处理与角色的关系数据,还有删除中间表 employee_role 中数据之后再往中间表 employee_role 中添加新的数据。
修改员工控制器

修改员工 Service 接口及实现类


修改员工 Mapper 接口及 XML


员工删除
除删除角色表此数据,还要从中间表 role_permission 中删除与此角色相关的数据。建议先删关系(防止设置了外键删除失败)
修改员工 Service 实现类

小伙砸,欢迎再看分享给其他小伙伴!共同进步!




