六 角色管理
1 思路
角色和权限是不同的,比如人事部内部可以有更详细的角色划分,人事专员,人事主管,人事总监等。中间表不需使用逆向工程生成实体,需要的时候定义即可
2 数据库表
① 角色表
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`sn` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
② 角色权限中间表 – 该表不能够设置主键(多对多关系)
CREATE TABLE `role_permission` (
`role_id` bigint(20) DEFAULT NULL,
`permission_id` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3 使用逆向工程
① 实体类
// getset便于后期字段维护
@Getter
@Setter
public class Role {
private Long id;
private String name;
private String sn;
}
② mapper接口
public interface RoleMapper {
int deleteByPrimaryKey(Long id);
int insert(Role record);
Role selectByPrimaryKey(Long id);
List<Role> selectAll();
int updateByPrimaryKey(Role record);
List<Role> selectForList(QueryObject qo);
// 此处可以通过mybatisx快速填写参数
void insertRelation(@Param("roleId") Long id, @Param("permissionId") Long permissionId);
void insertRelationBatch(@Param("roleId") Long id, @Param("permissionIds") Long[] permissionIds);
void deleteRelation(Long roleId);
}
③ mapper.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="cn.tj.mapper.RoleMapper" >
<resultMap id="BaseResultMap" type="cn.tj.domain.Role" >
<id column="id" property="id" />
<result column="name" property="name" />
<result column="sn" property="sn" />
</resultMap>
<delete id="deleteByPrimaryKey" >
delete from role
where id = #{id}
</delete>
<delete id="deleteRelation">
delete from role_permission where role_id=#{roleId}
</delete>
<insert id="insert" useGeneratedKeys="true" keyProperty="id" >
insert into role (name, sn)
values (#{name}, #{sn})
</insert>
<insert id="insertRelation">
insert into role_permission(role_id, permission_id) values (#{roleId},#{permissionId})
</insert>
<insert id="insertRelationBatch">
insert into role_permission(role_id,permission_id) values
<foreach collection="permissionIds" item="permissionId" separator=",">
(#{roleId},#{permissionId})
</foreach>
</insert>
<update id="updateByPrimaryKey" >
update role
set name = #{name},
sn = #{sn}
where id = #{id}
</update>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" >
select id, name, sn
from role
where id = #{id}
</select>
<select id="selectAll" resultMap="BaseResultMap" >
select id, name, sn
from role
</select>
<select id="selectForList" resultMap="BaseResultMap">
select id, name, sn
from role
</select>
</mapper>
4 service – 注意修改分页 – 角色新增
① 接口
public interface IRoleService {
void save(Role role, Long[] permissionIds);
void delete(Long id);
void update(Role role, Long[] permissionIds);
Role get(Long id);
List<Role> listAll();
// 查询分页方法
PageInfo<Role> query(QueryObject qo);
}
② 实现类
@Service
// @Slf4j
public class RoleServiceImpl implements IRoleService {
@Autowired
private RoleMapper roleMapper;
public void setRoleMapper(RoleMapper roleMapper) {
this.roleMapper = roleMapper;
}
// 需先插入角色表,再插入中间表,中间表需要角色id和权限id
@Override
@Transactional
public void save(Role role, Long[] permissionIds) {
// 向角色表中插入数据
roleMapper.insert(role);
// 维护中间表关系 必须插入角色后才会回填 id 应避免这种循环插入数据库的操作(优化)
/*for (Long permissionId : permissionIds) {
roleMapper.insertRelation(role.getId(),permissionId);
}*/
// 使用批量的时候要小心数据量过大,解决的方式就是批处理。(使用动态sql)
if(permissionIds != null && permissionIds.length > 0){
roleMapper.insertRelationBatch(role.getId(),permissionIds);
}
}
@Override
@Transactional
public void delete(Long id) {
// 删除中间表信息
roleMapper.deleteRelation(id);
// 删除自身信息
roleMapper.deleteByPrimaryKey(id);
}
@Override
@Transactional
public void update(Role role, Long[] permissionIds) {
// 修改角色信息
roleMapper.updateByPrimaryKey(role);
// 维护中间表关系(我们采取的就是先删后加策略)
// 删除中间表关系
roleMapper.deleteRelation(role.getId());
// 添加中间表关系
if(permissionIds != null && permissionIds.length > 0){
roleMapper.insertRelationBatch(role.getId(),permissionIds);
}
}
@Override
public Role get(Long id) {
return roleMapper.selectByPrimaryKey(id);
}
@Override
public List<Role> listAll() {
return roleMapper.selectAll();
}
@Override
public PageInfo<Role> query(QueryObject qo) {
PageHelper.startPage(qo.getCurrentPage(),qo.getPageSize());
return new PageInfo<Role>(roleMapper.selectForList(qo));
}
}
5 controller
@Controller
@RequestMapping("/role")
public class RoleController {
@Autowired
private IRoleService roleService;
// 需查询所有权限
@Autowired
private IPermissionService permissionService;
// 处理角色查询所有方法
@RequestMapping("/list")
@RequirePermission(name="角色列表",expression = "role:list")
public String list(Model model, QueryObject qo){
PageInfo<Role> pageInfo = roleService.query(qo);
model.addAttribute("pageInfo",pageInfo);
return "role/list"; // WEB-INF/views/ role/list .jsp
}
// 处理角色删除方法
@RequestMapping("/delete")
@RequirePermission(name="角色删除",expression = "role:delete")
public String delete(Long id){
if (id != null) {
roleService.delete(id);
}
return "redirect:/role/list";
}
// 进入角色新增/编辑页面方法 需回显角色,所有权限,拥有权限的信息
@RequirePermission(name="进入角色新增/编辑页面",expression = "role:input")
@RequestMapping("/input")
public String input(Long id,Model model){
// 查询所有的权限信息列表
List<Permission> allPermissions = permissionService.listAll();
model.addAttribute("allPermissions",allPermissions);
if (id != null) {
// 修改
// 当前角色的信息
Role role = roleService.get(id);
model.addAttribute("role",role);
// 根据角色id查询当前角色的权限集合
List<Permission> selfPermissions = permissionService.queryByRoleId(id);
model.addAttribute("selfPermissions",selfPermissions);
}
return "role/input";
}
// 此处前端返回参数需要使用数组进行接收(名字必须相同,不同接收不到)
// 角色新增方法
@RequestMapping("/saveOrUpdate")
@RequirePermission(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";
}
}
6 前端
① list.html – 角色列表
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>角色管理</title>
</head>
<body class="hold-transition skin-black sidebar-mini">
<div th:replace="common/fragment :: link"></div>
<div class="wrapper">
<div th:replace="common/fragment :: navbar"></div>
<div th:replace="common/fragment :: menu"></div>
<div class="content-wrapper">
<section class="content-header">
<h1>角色管理</h1>
</section>
<section class="content">
<div class="box">
<!--高级查询--->
<div style="margin: 10px;">
<!--高级查询--->
<form class="form-inline" id="searchForm" action="/role/list" method="post">
<input type="hidden" name="currentPage" id="currentPage" value="1">
<a href="/role/input" class="btn btn-success btn-input"><span class="glyphicon glyphicon-plus"></span> 添加</a>
</form>
<div class="box-body table-responsive ">
<table class="table table-hover table-bordered table-striped" >
<thead>
<tr>
<th>编号</th>
<th>角色名称</th>
<th>角色编号</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="role,start:${pageInfo.list}">
<td th:text="${start.count}">1</td>
<td th:text="${role.name}">部门经理</td>
<td th:text="${role.sn}">MGR</td>
<td>
<a class="btn btn-info btn-xs btn-input" th:href="|/role/input?id=${role.id}|">
<span class="glyphicon glyphicon-pencil"></span> 编辑
</a>
<a class="btn btn-danger btn-xs btn-delete" th:data-url="|/role/delete?id=${role.id}|">
<span class="glyphicon glyphicon-trash"></span> 删除
</a>
</td>
</tr>
</tbody>
</table>
<div th:replace="common/fragment :: page"></div>
</div>
</div>
</div>
</section>
</div>
<div th:replace="common/fragment :: footer"></div>
</div>
</body>
</html>
② input.html
角色新增较复杂故不使用模态框,新增时需添加角色和该角色有哪些权限。展示时角色列表不展示各角色拥有哪些权限(一个角色可能有很多权限,可设置查看权限按钮)。添加角色时,角色名称与编号是角色数据,分配权限左侧框体内为所有的可分配权限,右侧为可以被分配的权限
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>角色管理</title>
</head>
<body class="hold-transition skin-black sidebar-mini">
<div th:replace="common/fragment :: link"></div>
<div class="wrapper">
<div th:replace="common/fragment :: navbar"></div>
<div th:replace="common/fragment :: menu"></div>
<div class="content-wrapper">
<section class="content-header">
<h1>角色编辑</h1>
</section>
<section class="content">
<div class="box">
<form class="form-horizontal" action="/role/saveOrUpdate" method="post" id="editForm">
<!--?. 是Thymeleaf的安全导航操作符。role不是null获取role的id属性。role是null表达式的结果将为null,并且不会尝试访问id,从而避免可能的空指针异常。-->
<input type="hidden" name="id" th:value="${role?.id}">
<div class="form-group" style="margin-top: 10px;">
<label class="col-sm-2 control-label">角色名称:</label>
<div class="col-sm-6">
<input type="text" class="form-control" th:value="${role?.name}" name="name" placeholder="请输入角色名称">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">角色编号:</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="sn" th:value="${role?.sn}" placeholder="请输入角色编号">
</div>
</div>
<div class="form-group " id="role">
<label for="role" class="col-sm-2 control-label">分配权限:</label><br/>
<div class="row" style="margin-top: 10px">
<div class="col-sm-2 col-sm-offset-2">
<select multiple class="form-control allPermissions" style="height: 350px;" size="15">
<option th:each="permission:${allPermissions}"
th:text="${permission.name}"
th:value="${permission.id}"></option>
</select>
</div>
<div class="col-sm-1" style="margin-top: 60px;" align="center">
<div>
<a type="button" class="btn btn-primary" style="margin-top: 10px" title="右移动"
onclick="moveSelected('allPermissions', 'selfPermissions')">
<span class="glyphicon glyphicon-menu-right"></span>
</a>
</div>
<div>
<a type="button" class="btn btn-primary " style="margin-top: 10px" title="左移动"
onclick="moveSelected('selfPermissions', 'allPermissions')">
<span class="glyphicon glyphicon-menu-left"></span>
</a>
</div>
<div>
<a type="button" class="btn btn-primary " style="margin-top: 10px" title="全右移动"
onclick="moveAll('allPermissions', 'selfPermissions')">
<span class="glyphicon glyphicon-forward"></span>
</a>
</div>
<div>
<a type="button" class="btn btn-primary " style="margin-top: 10px" title="全左移动"
onclick="moveAll('selfPermissions', 'allPermissions')">
<span class="glyphicon glyphicon-backward"></span>
</a>
</div>
</div>
<div class="col-sm-2">
<select multiple class="form-control selfPermissions" style="height: 350px;" size="15" name="permissionIds">
<option th:each="permission:${selfPermissions}"
th:text="${permission.name}"
th:value="${permission.id}"></option>
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-6">
<button type="button" class="btn btn-primary btn-submit">保存</button>
<a href="javascript:window.history.back()" class="btn btn-danger">取消</a>
</div>
</div>
</form>
</div>
</section>
</div>
<div th:replace="common/fragment :: footer"></div>
</div>
<script>
// 全部左右移动
function moveAll(src, target) {
$('.' + target).append($('.' + src + ' > option'));
}
// 选中左右移动
function moveSelected(src, target) {
$('.' + target).append($('.' + src + ' > option:selected'));
}
// 点击保存按钮
$('.btn-submit').click(function () {
// 提交表单前获取右侧选框中的所有权限,防止用户丢失选中,将其全部设为选中
$('.selfPermissions > option').prop('selected',true);
$('#editForm').submit();
})
// 一进入页面就需要进行去重
$(function () {
// 下拉列表去重 角色回显的已拥有权限,应从全部权限中去除
// 获取角色的全部权限
// map会进行遍历以kv的形式将数据存到数组中,最后需要return数据,该方法会返回一个数组(不用pushl)
var arr = $('.selfPermissions > option').map(function (index, domEle) {
// 此处获得的是JQ对象
return $(domEle).val();
})
// 与所有权限遍历做比对
$('.allPermissions > option').each(function (i, e) {
// 判断每次遍历的元素是否在上面的数组(角色权限)中出现过?(出现代表重复,需要删除)
// inArray参数为(每次遍历的元素,查看是否存在在其中的数组 判断条件)
if($.inArray($(e).val(),arr) >= 0){
// 若存在则返回索引下标,说明重复了。我们要将该元素删除,不存在则返回-1
$(e).remove();
}
})
})
</script>
</body>
</html>
7 IPermissionService 接口 – 角色编辑所需查询权限方法
public interface IPermissionService {
......
/**
* 根据角色 id 查询权限集合
* @param roleId 角色 id
* @return List<Permission> 权限集合
*/
List<Permission> queryByRoleId(Long roleId);
}
8 PermissionServiceImpl 实现类
@Service
public class PermissionServiceImpl implements IPermissionService, ApplicationContextAware {
......
@Override
public List<Permission> queryByRoleId(Long roleId) {
return permissionMapper.queryByRoleId(roleId);
}
}
9 PermissionMapper
@Repository
public interface PermissionMapper {
......
List<Permission> queryByRoleId(Long roleId);
}
10 PermissionMapper.xml – 子查询
......
<select id="queryByRoleId" resultMap="BaseResultMap">
select id,name,expression from permission where id in (
select permission_id from role_permission where role_id = #{roleId})
</select>
11 RoleController – 修改时可更改角色信息以及权限信息(都要传)
// 进入角色新增/编辑页面方法
@RequirePermission(name="进入角色新增/编辑页面",expression = "role:input")
@RequestMapping("/input")
public String input(Long id,Model model){
// 所有的权限信息列表
List<Permission> allPermissions = permissionService.listAll();
model.addAttribute("allPermissions",allPermissions);
if (id != null) {
// 修改
// 当前角色的信息
Role role = roleService.get(id);
model.addAttribute("role",role);
// 当前角色的权限集合
List<Permission> selfPermissions = permissionService.queryByRoleId(id);
model.addAttribute("selfPermissions",selfPermissions);
}
return "role/input"; // WEB-INF/views/ role/input .jsp
}
// 角色新增方法 单击保存时需传递多个参数
@RequestMapping("/saveOrUpdate")
@RequirePermission(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"; // 再次发起请求 到我们上面的查询所有的控制器方法。
}
12 IRoleService
void update(Role role, Long[] permissionIds);
13 RoleServiceImpl
@Override
@Transactional
public void update(Role role, Long[] permissionIds) {
// 修改角色信息
roleMapper.updateByPrimaryKey(role);
// 维护中间表关系(我们采取的就是先删后加策略)
// 删除中间表关系(根据roleId)
roleMapper.deleteRelation(role.getId());
// 添加中间表关系 permissionIds是数组,若数组为空,插入方法调用mapper的动态sql就会出错,所以需要判断
// 首先不能为null而且长度大于0
if(permissionIds != null && permissionIds.length > 0){
roleMapper.insertRelationBatch(role.getId(),permissionIds);
}
}
14 RoleMapper – deleteRelation 方法 删除中间表数据
void deleteRelation(Long roleId);
15 RoleMapper.xml – deleteRelation
<delete id="deleteRelation">
delete from role_permission where role_id=#{roleId}
</delete>
16 Role的list.html – 角色删除
<!--此处使用data-url自定义属性进行数据传递-->
<a class="btn btn-danger btn-xs btn-delete" th:data-url="|/role/delete?id=${role.id}|">
<span class="glyphicon glyphicon-trash"></span> 删除
</a>
<!--调sweetalter2删除框-->
$(function () {
$('.btn-delete').click(function () {
var url = $(this).data('url');
Swal.fire({
title: '您确定要删除吗?',
text: "此操作不可撤销!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then((result) => {
if(result.value) {
location.href = url;
}
});
});
17 RoleController
// 处理角色删除方法
@RequestMapping("/delete")
@RequirePermission(name="角色删除",expression = "role:delete")
public String delete(Long id){
if (id != null) {
roleService.delete(id);
}
return "redirect:/role/list";
}
18 IRoleService
void delete(Long id);
19 RoleServiceImpl
@Override
@Transactional
public void delete(Long id) {
// 此处应先删中间表的数据(中间表使用他表信息)
// 删除中间表信息
roleMapper.deleteRelation(id);
// 删除自身信息
roleMapper.deleteByPrimaryKey(id);
}
20 角色管理小结
- 角色列表
- 创建 role 表及中间表 role_permission,通过逆向工程生成所需要的代码,完成 Service.Controller 代码。
- 完成 role/list 页面的遍历。
- 角色新增
- 在进入新增页面之前,我们需要查询所有的权限集合。并放入作用域中。
- 在前台页面我们就可以将所有的权限集合展示出来。
- 完成前台页面角色的 js 左右移动。
- 因为按钮是 button 。我们需要写一个 jQuery 调用 submit() 方法提交表单。提交之前我们需要将所有右侧下拉列表中的 option 进行选中。防止用于不选中就直接提交。
- 在后台保存的时候接收中间表的 权限数组。
- 调用插入方法时必须先插入角色。(因为只有插入了角色后才有角色 id)
- 维护中间表关系。(优化后为动态 SQL 插入)
- 角色编辑
- 在进入角色编辑页面之前。我们需要回显3组数据。(所有的权限集合,当前角色的信息,当前角色的权限集合(子查询或多表连接))。
- 前台在页面加载完成之后 立即去做去重操作。
- 在后台我们编辑时使用的是 先将所有原始权限删除,在添加新的权限。
- 角色删除
- 别忘记维护中间表关系
- 要先删除中间表,在删除角色。