|导读
在第四节中我们开发了2个接口,然而我们并没有编写对应的操作界面,这一讲我将带你开发一个对应后台接口的管理界面,在编写界面之前我们需要了解一下matrix中的权限管理,因为谁能看到什么界面是受到权限的管理的,不是每个人都能看到所有的界面,而应该是不同角色的人看到的界面不同,这就需要我们对界面和角色直接进行一个管理。
有了角色还不行,我们还需要用户,所以用户和角色应该有一个对应关系。一个用户应该拥有一个或者多个角色。用户有了角色就有了菜单了
我们再往一个通用一点的场景想想,这个应用是否可以给多个公司使用,每个公司的权限不同,不同的公司可以给公司自己的员工分配公司层面所拥有的权限。 有的读者可能会问为什么多此一举设计一个公司的概念。我是基于这样一种考虑,对一个产品而言,销售的时候很可能分为初级版,中级版,高级版。可能使用初级版本的公司只有很小的一部分功能,而高级版的就会有很多功能,初级版的用户有升级到高级版的需求,这样一来就要求我们可以灵活的对一群人分配权限,我们就可以通过公司这样一概念对一群人进行权限的分配从而满足这样的业务场景。所以我们的功能权限应该首先分配给公司,再由公司分配给公司的成员。在matrix中给公司分配权限的人为super账号超级用户,给公司成员分配权限的人为admin 账号管理员账号。
有了super和admin 那么在请大家想想功能从哪里来?
功能就是开发人员开发的,开发后要告诉matrix你开发的功能名称是什么,访问路径是什么这样matrix才能帮你管理起来完成功能的分配和调用。那么我们这里就讲出了最后的一个角色zking这个开发者账号,这个账号可以录入和修改功能的基础信息。
| 创建功能
之前我们开发的功能如果需要正在菜单上显示必须先登录开发者账号(zking)注册功能,然后把功能分配给公司,然后才能在公司管理员的菜单上显示。
使用开发者账号(zking,密码:123)登录matrix 后进入 系统管理》功能管理 菜单就可以对功能和按钮进行管理. 下图是功能的新增界面
注:对于我们开发的简单的商品管理功能我们只需要填写功能相关的信息,对于按钮相关的信息只要勾选搜索,和删除即可不需要填写具体的访问路径,其他功能在后续的输入学习中我们才能使用到。
功能表单说明
字段 |
说明 |
父级功能 |
管理功能的父子关系,目前仅支持 2 级 |
排序 |
|
功能名称 |
功能名称 |
功能图标 |
功能图标 |
菜单路径 |
菜单路径是指的是功能访问路径如果是菜单功能在 index.jsp 中会默认拼接访问地址 |
当前状态 |
|
功能 code 功能的 code 需要保持唯一。功能 code 就是功能的标识。通过功能 code 和按钮的 code 我们可以判断一个用户是否具有改功能下的按钮权
限。
我们通过功能 code 和按钮 code 来定位唯一一个按钮功能 |
|
是否在菜单显示 |
|
功能按钮 |
|
| 用户分配权限
功能新增后我们可以登录超级管理员(super,123),对功能进行分配。把功能分配到具体的一个公司,如下图
公司的超级管理员默认拥有该公司下所有的权限,不需要额外配置。给公司分配好权限后,登录公司的管理员(admin,123)可以看到系统用户和角色管理 2 个功能。
公司管理员可以通过创建不同的角色来给用户分配功能。它们关系是:
一个用户拥有多个角色
一个角色拥有多个功能
一个功能拥有多个按钮
登录admin后我们可以给角色授权功能,如果没有角色则在角色管理中新增一个角色
有了角色后我们给用户设置角色
最后登录这个分配了角色的用户后我们就可以在菜单上找到这个功能,点击后就可以访问了。
| 示例
通过上面的讲解大家对matrix的权限有了一定的了解,也知道了如何才能访问到菜单了,下面我们对前面的例子进行功能菜单的配置
1、创建一个demo-list.jsp作为功能界面 ,放在/app-web/src/main/webapp/WEB-INF/views/admin/biz/demo-list.jsp路径下
首先我们只写一个很简单的提示语句
2、登录zking创建功能
3、登录super给公司分配权限
4、登录公司的管理员账号admin 使用功能
| JSP界面的编写
通过上述的讲解,我们已经能够通过matrix的权限配置使得用户访问我们的界面了,那么界面到底该如何编写了,下面我们通过用户管理界面的解析来说明matrix中一个基本的表格界面的构成,表格界面是后台管理系统的基础组成单元,所以我们这里重点讲解,后续复杂一点的变体界面,读者在懂了基础版本后可以自己发挥。
下面是用户管理的jsp界面源码,我们对照上面的结构图分析一下注意注释部分。
<%-- jsp的基本标签引入,和访问路径变量path,以及自定义标签matrix的引入 --%>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
<%@ taglib uri="http://www.zkingsoft.com" prefix="matrix"%>
<c:set var="path" value="${pageContext.request.contextPath }" />
<%-- End --%>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<!-- 本框架基本脚本和样式 -->
<script type="text/javascript" src="${path }/resource/js/plugin/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="${path }/resource/js/systools/MBase.js"></script>
<%-- End --%>
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeInRight">
<!-- 搜索框部分start -->
<matrix:btn value="user:search">
<div class="row">
<div class="col-sm-12">
<form class="form-inline" id="serchform">
<div class="input-group">
<div class="btn-group search-list " data-for="search-text">
<button type="button"
class="btn btn-default dropdown-toggle searchlist"
data-toggle="dropdown">
用户姓名 <span class="caret "></span>
</button>
<ul class="dropdown-menu" role="menu">
<li data-field="suName"><a>用户姓名</a></li>
<li data-field="suAccount"><a>账号 </a></li>
</ul>
</div>
<div class="form-group mr-20">
<input id="search-text" name="suName" placeholder="输入查询关键词"
type="text" class="form-control">
</div>
<div class="form-group mr-20">
<button onclick="myGrid.serchData(1)" type="button"
class="btn btn-info">
<i class="fa fa-search "></i> 搜索
</button>
<button type="reset" class="btn btn-info ">
<i class="fa fa-refresh "></i> 重置
</button>
</div>
</div>
</form>
</div>
</div>
</matrix:btn>
<!-- 搜索框部分en -->
<div class="ibox-content radius-5 mt-5 mpanel">
<div class="row">
<div class="col-sm-12">
<!-- 功能按钮部分 -->
<div class="option-bar">
<matrix:btn value="user:dels">
<button onclick="myGrid.delItems('suId')" type="button"
class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i>批量删除
</button>
</matrix:btn>
<matrix:btn value="user:add">
<button onclick="openAdd()" type="button"
class="btn btn-success btn-sm">
<i class="fa fa-plus"></i> 新增
</button>
</matrix:btn>
</div>
<!-- 功能按钮部分end -->
<!-- 数据表格部分 -->
<table id="mgrid">
<thead>
<tr>
<th data-checkbox="true"></th>
<th data-formatter="MGrid.indexfn" data-align="center"
data-width="30px">序号</th>
<th data-field="suName" data-sortable="true">姓名</th>
<th data-field="companyName">所属公司</th>
<th data-field="suAccount">账号</th>
<th data-field="suTel">电话</th>
<th data-field="suRegisterTime" data-formatter="MGrid.getTime"
data-sortable="true">注册时间</th>
<th data-align="center" data-width="150px" data-field="suId"
data-formatter="buidOperate">操作</th>
</tr>
</thead>
</table>
<!-- 数据表格部分end -->
</div>
</div>
</div>
</div>
<script type="text/javascript"
src="${path }/resource/js/systools/MJsBase.js"></script>
<script type="text/javascript">
//定义表格对象
var myGrid;
$(function() {
//定义删除数据的路径
var delPath = "";
//使用权限标签控制路径是否显示在界面上
<matrix:btn value="user:del">
delPath = "${path}/do/admin/su/del";
</matrix:btn>
//初始化表格对象
myGrid = MGrid.initGrid({
url : "${path}/do/admin/su/showList",//url数据加载地址,表格初始化后会自动去后台加载数据。
delUrl : delPath
});
});
//构建操作栏的按钮
function buidOperate(value, row, index) {
var html = [];
<matrix:btn value="user:edit">
html[0] = '<a onClick="openEdit(\'' + value
+ '\')" title="编辑" class="fa fa-edit option"></a>'
</matrix:btn>
<matrix:btn value="user:resetPassword">
html[1] = '<a onClick="resetPassword(\'' + value
+ '\')" title="密码重置" class="fa fa-refresh "></a>';
</matrix:btn>
<matrix:btn value="user:accountLock">
if (row.suAccountStatus == 'locked') {
html[2] = '<a onClick="unlock(\'' + value
+ '\')" title="解锁" class="fa fa-unlock option "></a>';
} else if (row.suAccountStatus == 'activate') {
html[2] = '<a onClick="lock(\'' + value
+ '\')" title="锁定" class="fa fa-lock option "></a>';
}
</matrix:btn>
<matrix:btn value="user:del">
html[3] = '<a onClick="myGrid.delItem(\''
+ value
+ '\')" title="删除" class="fa fa-close option text-danger"></a>';
</matrix:btn>
return html.join("");
}
<matrix:btn value="user:add">
//打开添加界面
function openAdd() {
layer.open({
type : 2,
title : "添加管理员",
maxmin : true,
area : [ MUI.SIZE_L, '500px' ],
content : [ '${path}/do/admin/su/editForm' ]
});
}
</matrix:btn>
<matrix:btn value="user:edit">
//打开编辑界面
function openEdit(id) {
layer.open({
type : 2,
title : "编辑管理员",
area : [ MUI.SIZE_L, '500px' ],
maxmin : true,
content : [ '${path}/do/admin/su/editForm?id=' + id ]
});
}
</matrix:btn>
<matrix:btn value="user:resetPassword">
function resetPassword(id) {
MTools.handleItem('${path}/do/admin/su/resetPassword?id=' + id,
"确认要重置密码吗?");
}
</matrix:btn>
<matrix:btn value="user:lock">
function unlock(id) {
MTools.handleItem('${path}/do/admin/su/accountLock/unlock/?id='
+ id, "确认要解锁账号吗?", function() {
myGrid.serchData();
});
}
function lock(id) {
MTools.handleItem('${path}/do/admin/su/accountLock/lock/?id=' + id,
"确认要锁定账号吗?", function() {
myGrid.serchData();
});
}
</matrix:btn>
</script>
</body>
</html>
| jsp 界面按钮权限控制
在 jsp 界面上我们需要根据不同的角色显示不同的按钮或者 html 组件,matrix 通过 jsp 标签来控制这些元素的显示
1、首先需要在 jsp 中引入标签库
<%@ taglib uri="http://www.zkingsoft.com" prefix="matrix"%>
2、把需要被控制的代码用标签包裹起来,如控制一个新增按钮,
<matrix:btn value="user:add">
<button > 新增</button>
</matrix:btn>
<matrix:btn value="user:add">表示判断当前登录用户是否具有一个功能code为user按钮key为add的按钮功能,如果有则显示标签中内容,如果没有则不显示标签中内容。具体实现参见【com.zkingsoft.common.authority.tag.ButtonAuthorutyTag】类
小贴士
标签可以用来控制 js,因为我们会在 js 中暴露一些接口的访问路径,如果让没有权限的人看到了这些路径我们觉得是不安全的。所以包含接口路径的 js 也可以用标签包裹起来。
jsp 界面功能权限控制
一些非菜单功能也是需要用标签来控制的我们也提供了标签来管理
<matrix:fn value="user">
<a href=”#” > 用户管理</a>
</matrix:fn>
原理和按钮功能的控制一样,这里表示当前登录人是否具有功能 code 为 user 的功能。具体实现参见【com.zkingsoft.common.authority.tag.FunctionAuthorutyTag】
|服务端权限拦截
无论前端如何拦截防止暴露接口地址,总不能 100%避免接口地址被别人知道。所以对于安全性要求较高的接口我们必须在服务端进行权限校验,而不是单纯依靠前端的限制。Matrix 通过url 拦截认证的方式校验 url 访问权限,这样做的好处是对业务代码没有侵入性。相对于 matrix3中的硬编码式权限认证有了很大的扩展性提高,为实现无状态应用提供了基础。权限拦截器配置在 spring-mvc-content.xml 中
<!-- url权限拦截 -->
<mvc:interceptor>
<mvc:mapping path="/**/su/**" />
<bean class="com.zkingsoft.common.interceptor.AuthorityInterceptor" />
</mvc:interceptor>
权限拦截器会拦截所有包含 su 路径的请求,还记得我们在《创建系统功能》一节中提到的按钮路径吗?如/admin/sysRole/su/editForm 就是一个新增角色的按钮路径,路径中包含 su,表示这个路径是要被权限拦截器拦截认证的,只有认证通过才能访问,否则会提示权限不足。实现原理有兴趣的同学可以参考拦截器源码。
在我们开发的功能中给接口加上 su 路径就可以被权限拦截器拦截,如下
@RequestMapping(value =SAFEPATH+"/del")
public @ResponseBody AjaxResult del(String keys) {return remove(sysRoleService, keys);
}
小贴士
SAFEPATH 是定义在 com.zkingsoft.common.constance.中的常量,我们可以在 action 中静态导入这类。import static com.zkingsoft.common.constance.AppConstance.*;