权限系统控制概述
权限管理基于角色访问控制(RBAC:Role Based Access Control),这种模型的基本概念是把权限(Permission)与角色(Role)联系在一起,用户通过充当合适角色从而获得该角色所拥有的权限
PageHelper 插件
-
添加依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency>
-
配置分页插件
#在application.properties中配置 pagehelper.reasonable=true pagehelper.page-size-zero=true
-
使用分页插件提供的 PageInfo 类进行分页7要素的封装,替换掉我们自己写的PageResult类,在需要进行分页的查询方法前调用PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个查询方法会进行分页
@Override public PageInfo<Department> query(QueryObject qo) { PageHelper.startPage(qo.getCurrentPage(),qo.getPageSize()); List<Department> departments = departmentMapper.selectForList(qo); return new PageInfo<>(departments); }
前端 twbs-pagination 分页插件
-
导入相关js文件
-
在想要进行分页条显示的地方添加以下代码
<ul id="pagination" class="pagination"></ul> <script th:inline="javascript"> //分页 $(function(){ var totalPages = /*[[${pageInfo.pages}]]*/ 1; //如果${pageInfo.pages}为空,就把1赋给totalPages var startPage = /*[[${pageInfo.pageNum}]]*/ 1;//如果${pageInfo.pageNum}为空,就把1赋给startPage $('#pagination').twbsPagination({ totalPages: totalPages, startPage:startPage, first:'首页', prev:'上一页', next:'下一页', last:'尾页', visiblePages: 5, onPageClick: function (event, page) { console.log(page);//点击哪一页,就打印当前点击的页码 //给隐藏按钮的value属性赋值 $('#currentPage').val(page); //提交表单 $('#searchForm').submit(); } }); }) </script>
-
如果有多个页面也需要分页功能,可以通过Thymeleaf 的fragment 方式进行抽取
//要抽取的代码放入下面的标签中 <div th:fragment="page" style="text-align: center;"> ............ </div> //页面通过Thymeleaf的th:replace对以上抽取的代码块进行引用 <div th:replace="common/fragment :: page"></div>
-
Bootstrap 模态框
部门添加
- 将模态框放在页面 body 元素的直接子元素中即可
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">部门编辑</h4>
</div>
<form action="/department/saveOrUpdate" method="post">
<input type="hidden" name="id">
<div class="modal-body">
<div class="form-group">
<label for="name">名称</label>
<input type="text" class="form-control" name="name" id="name" placeholder="名称">
</div>
<div class="form-group">
<label for="sn">缩写</label>
<input type="text" class="form-control" name="sn" id="sn" placeholder="缩写">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">保存</button>
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</form>
</div>
</div>
</div>
- 通过给添加按钮绑定点击事件,点击之后弹出模态框
<script>
$(function () {
$('.btn-input').click(function () {
$('.modal').modal('show');
})
})
</script>
<a href="#" class="btn btn-success btn-input">添加</a>
部门修改
编辑回显,当用户点击编辑按钮的时候,在模态框中回显要编辑的数据
-
使用jquery的API实现
-
用AJAX,通过获取被修改数据的id值,处理方法中根据id查询部门数据返回JSON
-
把数据统一藏在编辑按钮上的data-json属性,点击编辑按钮后,在事件中获取该按钮 data-json 属性中的值,并使用 DOM 操作回显在模态框中
-
在对应的实体类中添加一个 get 方法,用于返回 JSON 字符串数据
$(function () {
$('.btn-input').click(function () {
var $data = $(this);
//{name: '人力部', id: 1, sn: 'hr'}
var data = $data.data('json');
//console.log(data);
//清除模板框中的value
$('.modal input').val('');
if(data){//如果有数据才进行回显
$('input[name=id]').val(data.id);
$('input[name=name]').val(data.name);
$('input[name=sn]').val(data.sn);
}
$('.modal').modal('show');//官方文档中表示通过该方法即可弹出模态框
})
})
<a href="#" th:data-json="${d.json}" class="btn btn-info btn-xs btn-input">编辑</a>
public String getJson() throws JsonProcessingException {
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("sn",sn);
return new ObjectMapper().writeValueAsString(map);
}
部门删除
软删除:
(del字段为bit类型,如果被删除了,就为true,没有被删除,就为false)
通过给数据库表中增加一列字段,类型为boolean,如果点击了删除按钮,执行操作时sql语句的编写为:
<update id="delete"> update department set del = 1 where id = #{
id}</update>
在查询时,由于页面不再显示被删除的数据,因此在查询时需要加过滤条件,只查询del字段为false的数据:
<select id="selectForList" resultType="cn.kjcoder.domain.Department"> select id,name,sn from department where del = false</select>
硬删除:
使用弹框框架,引入弹框依赖文件,使用官方提供的案例进行修改
<a th:url="|/department/delete?id=${d.id}|" class="btn btn-danger btn-xs btn-delete"> 删除 </a>
$('.btn-delete').click(function () {
//获取删除按钮的url属性
var url = $(this).attr('url');
Swal.fire({
title: '您确定要删除吗?',
text: "此操作不可撤销!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then((result) => {
console.log(result.value);//是否删除,是输出true,否则输出未定义
if(result.value) {
// 点了确定做什么,由开发者决定
location.href = url;
}
});
})
如果多个页面也需要进行删除操作,可以把删除的操作代码单独抽取到一个js文件中
权限加载
- 通过点击权限加载按钮发送异步请求
- 后台处理方法对请求进行处理,通过反射获取所有控制器对象的处理方法(可以通过应用上下文获取到容器中的所有bean对象|控制器对象|),获取每个控制器字节码对象,反射获取其中的方法,并且拿到方法上的自定义注解,把注解里面属性值取出并封装到Permission对象中之后批量保存到数据库权限表中,并返回json数据
- 若贴有自定义注解,则从注解中获取权限名称和权限表达式,还要判断这个方法的权限表达式是否已经存在数据库中,若该权限表达式不存在数据库,则创建 Permission 对象,封装数据并存入数据库中,若已存在,则不进行保存操作(实体类需重写hashCode和equals方法)
- 前端通过回调函数获取后端返回的json数据,若成功跳转查询所有,若加载失败,提示友好信息
<!--html-->
<script>
$(function () {
$('.btn-reload').click(function () {
Swal.fire({
title: '您确定要加载权限吗?',
text: "此操作不可撤销!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then((result) => {
//console.log(1);
if(result.value){
$.post('/permission/load',function (data) {
//console.log(data);
if(data.success){
location.href = '/permission/list';
}else{
alert(data.msg);
}
})
}
});
})
})
</script>
//自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {
String name();
String expression();
}
@Autowired
private ApplicationContext ac;
@RequestMapping("/load")
@ResponseBody
public JsonResult load(){
List<Permission> permissions = permissionService.listAll();
//存要被保存的权限对象
Set<Permission> savePermissions = new LinkedHashSet<>();
try {
Map<String, Object> map = ac.getBeansWithAnnotation(Controller.class);
Collection<Object> controllers = map.values();
//遍历所有控制器对象
for (Object controller : controllers) {
//获取每个控制器字节码对象,反射获取其中的方法
Method[] methods = controller.getClass().getDeclaredMethods();
for (Method method : methods){
//获取方法上的自定义注解
RequiredPermission annotation = method.getAnnotation(RequiredPermission.class);
//若方法贴了注解,获取注解属性值封装到permission对象中
if(annotation != null){
Permission permission = new Permission();
permission.setName(annotation.name());
permission.setExpression(annotation.expression());
//不存在,才往集合中添加
if(!permissions.contains(permission)){
savePermissions.add(permission);
}
}
}
}
/*if(savePermissions.size() > 0){
//批量保存
permissionService.batchSave(savePermissions);
}*/
permissionService.batchSave(savePermissions);
} catch (Exception e) {
return new JsonResult(false,"权限加载失败");
}
return new JsonResult(true,"权限加载成功");
}
//映射配置文件中进行批量保存操作
<insert id="insertPermission">
insert into permission (name, expression) values
<foreach collection="savePermissions" item="p" separator=",">
(#{p.name},#{p.expression})
</foreach>
</insert>
角色保存
- 应前端页面需要权限数据,可以在controller将所有权限查询出来存入model,页面通过Thymeleaf 将所有权限显示在左边下拉框中
@RequestMapping("/input")
@RequiredPermission(name="角色新增或修改",expression="role:saveOrUpdate")
public String input(Long id, Model model){
if (id != null) {
// 表示去修改
Role role = roleService.get(id);
model.addAttribute("role", role);
}
List<Permission> permissions = permissionService.listAll();
model.addAttribute("permissions",permissions);
return "role/input";
}
- 点击保存按钮,提交表单,在这之前要选中右边下拉框中的所有option,因为下拉框只会提交选中的数据,若没有选中的数据是不会提交的,这样会导致右边没有被选中就提交不了要保存的权限信息
(修改按钮为普通按钮,通过绑定点击事件,在事件中把右边的 select 元素中的 option 设置为选中后,再提交表单)
$(function () {
//点击保存按钮,提交表单
$('.btn-submit').click(function () {
//把右边的所有复选框的selected属性都设为true
$('.selfPermissions > option').prop('selected',true);
//提交表单
$('#editForm').submit();
})
})
- 接收对应参数,调用业务方法,业务方法中除了往role表插入数据外,还需要往中间表插入关系数据
//controller层
@RequestMapping("/saveOrUpdate")
@RequiredPermission(name="角色新增或修改",expression="role:saveOrUpdate")
public String saveOrUpdate(Role role,Long[] permissionIds){
if(role.getId() == null){
roleService.save(role,permissionIds);
}else {
roleService.update(role,permissionIds);
}
return "redirect:/role/list";
}
//service
@Override
public void save(Role role, Long[] permissionIds) {
roleMapper.insert(role);
//除了录入角色表的数据外,还要往中间表中录入数据
if(permissionIds != null && permissionIds.length > 0){
for (Long permissionId : permissionIds) {
roleMapper.insertRelation(role.getId(),permissionId)<