1. 权限概述
1.1 权限概念
- “权”:能做的事情
- “限”:限制
- 对登录系统的用户进行操作、行为方面的限制,对项目中的功能进行保护,让没有权力访问的用户不能访问。
1.2 具体概念
1.2.1 资源
项目中被保护的功能就是资源。
资源可以体现为多种形式:
-
URL地址
http://localhost:8080/oa/save/emp.html
-
Java方法
AdminServiceImpl.saveAdmin()
-
页面元素
<a href="xxx">Show Something secret</a> <form ... <img ...
根据资源保护方式不同,可以把权限控制分成两部分:
-
粗粒度权限控制
使用类似Filter或Interceptor这样的机制,在项目运行的宏观层面从总体上控制每一个请求是否有权限访问一个资源。
-
细粒度权限控制
把用户不能操作的功能隐藏起来,让用户看不到不能访问的资源。
1.2.2 权限( 狭义)
权限 | 操作 |
---|---|
更新 | http://localhost:8080/oa/emp/list |
http://localhost:8080/oa/emp/to/edit |
|
http://localhost:8080/oa/emp/update |
|
删除 | http://localhost:8080/oa/emp/list |
http://localhost:8080/oa/emp/remove |
将完成一个功能所需要使用到的各个资源封装到一起,作为整体分配给用户,用户才能够完整使用这个功能。
封装到一起的多个资源称为权限(狭义)。
1.2.3 角色
角色可以看做用户分组,同一个角色就是相同类别的用户划分到一起。将相同类别的用户放在一起,统一操作就可以简化功能开发、简化系统管理员操作。
在比较复杂的功能需求下,角色会进一步细分。
- 角色1
- 子角色1
- 分组1
- 子分组1
- 子分组2
- 分组2
- 子分组3
- 子分组4
- 分组1
- 子角色2
- 分组3
- 分组4
- 子角色1
- 角色2
- 子角色3
- ……
- 子角色4
- ……
- 子角色3
- 角色3
- ……
- ……
1.2.4 用户
使用系统的人登录系统时使用的账号、密码。尚筹网项目中有两大类用户:
- 登录后台管理系统的用户:Admin
- 登录前台会员系统的用户:Member
用户可以看做使用系统的人在操作系统中具体功能的时候的具体身份。系统根据这个身份记录日志、控制权限。
1.3 关联关系
1.3.1 权限→资源
数据库表:多对多关系
一个权限往往包含多个资源
一个资源很有可能会被分配给多个不同权限
Java类:单向
- 关心一个权限中包含哪些资源,所以Java类方面权限对象能够获取到包含的资源即可。
- 不关心一个资源被分配给哪些权限了
1.3.2 角色→权限
数据库表:多对多关系
- 一个角色使用系统时会用到很多具体功能,这些功能必须都有权限才可以。
- 一个权限往往会被分配给多个不同角色。
Java类:单向
- 关心一个角色包含哪些权限,需要通过角色对象获取对应的权限。
- 不关心权限被分配给了哪些角色。
1.3.3 用户→角色
数据库表:多对多关系
- 一个角色是用户分类,同一个分类下往往包含很多具体用户。
- 一个用户有可能身兼数职,同时具备多个角色。
Java类:双向
- 关心角色中包含哪些用户。有可能在管理用户数据时列出某个角色下所有用户。
- 关心用户包含哪些角色,从而决定这个用户是否可以访问某个资源。
1.4 数据库中表示多对多
结论:需要用到中间表。
1.4.1 如果不使用中间表
1.4.2 如果使用中间表
1.5 中间表设置主键方式
1.5.1 方案一:另外设置专门字段
1.5.2 方案二:使用联合主键
两个字段(或多个字段)联合起来共同构成主键。构成联合主键的具体字段可以有重复值,联合主键合并的值不能重复。
2. Ajax
2.1 像提交普通表单一样发送数据
2.1.1 浏览器端代码
$(function(){
$("button").click(function(){
// 真正作为请求体发送给服务器的数据。
// 在我们像提交表单一样发送数据时,不把JSON对象转换为JSON字符串
var requestBody = {
"empId":999,
"empName":"harry",
"empSalary":128.56
};
$.ajax({
"url":"ajax/send/form/data", // 请求的目标地址
"type":"post", // 请求方式
"data":requestBody, // 请求体数据
"contentType":"application/x-www-form-urlencoded", //默认值,可以省略该项
"dataType":"json", // 服务器端返回数据的解析方式
"success":function(response) { // 服务器处理请求成功,执行这个函数,响应体从参数这里传入
// response已经解析为JSON对象,可以直接使用“.属性名”方式访问具体属性
var result = response.result;
// 如果返回结果成功,显示成功(逻辑成功)
if("SUCCESS" == result) {
alert("SUCCESS");
}
// 如果返回结果失败,显示消息(逻辑失败)
if("FAILED" == result) {
var message = response.message;
alert(message);
}
},
"error":function(response){ // 处理请求失败,例如:404、500、400等等
alert(response);
}
});
});
});
2.1.2 使用开发者工具查看请求体
2.1.3 handler代码
@ResponseBody
@RequestMapping("/ajax/send/form/data")
public ResultEntity<String> receiveFormData(Employee employee) {
System.out.println(employee);
return ResultEntity.successWithoutData();
}
2.2 整个请求体是一个JSON数据
2.2.1 浏览器端代码
$(function(){
$("button").click(function(){
// Ajax请求的请求体如果是JSON格式,那么需要先将数据封装为JSON对象
var jsonObj = [
{
"empId":666,
"empName":"jerry",
"empSalary":123.321
},
{
"empId":555,
"empName":"bob",
"empSalary":456.654
},
{
"empId":444,
"empName":"kate",
"empSalary":666.666
}
];
// 将JSON对象转换为JSON字符串
var requestBody = JSON.stringify(jsonObj);
// 发送Ajax请求
$.ajax({
"url":"ajax/send/json/data", // 请求的目标地址
"type":"post", // 请求方式
"data":requestBody, // 请求体数据
"contentType":"application/json;charset=UTF-8", //非默认值,不能省略
"dataType":"json", // 服务器端返回数据的解析方式
"success":function(response) { // 服务器处理请求成功,执行这个函数,响应体从参数这里传入
// response已经解析为JSON对象,可以直接使用“.属性名”方式访问具体属性
var result = response.result;
// 如果返回结果成功,显示成功(逻辑成功)
if("SUCCESS" == result) {
alert("SUCCESS");
}
// 如果返回结果失败,显示消息(逻辑失败)
if("FAILED" == result) {
var message = response.message;
alert(message);
}
},
"error":function(response){ // 处理请求失败,例如:404、500、400等等
alert(response);
}
});
});
});
2.2.2 使用开发者工具查看请求体
2.2.3 handler代码
@ResponseBody
@RequestMapping("/ajax/send/json/data")
public ResultEntity<String> receiveJsonData(@RequestBody List<Employee> empList) {
for (Employee employee : empList) {
System.out.println(employee);
}
return ResultEntity.successWithoutData();
}
2.3 表单提交list数据[反面教材]
2.3.1 浏览器端代码
$(function(){
$("button").click(function(){
// Ajax请求的请求体如果是JSON格式,那么需要先将数据封装为JSON对象
var jsonObj = {
"empList[0].empId":666,
"empList[0].empName":"jerry",
"empList[0].empSalary":123.321,
"empList[1].empId":555,
"empList[1].empName":"bob",
"empList[1].empSalary":456.654,
"empList[2].empId":444,
"empList[2].empName":"kate",
"empList[2].empSalary":666.666
};
// 发送Ajax请求
$.ajax({
"url":"ajax/send/form/list/data", // 请求的目标地址
"type":"post", // 请求方式
"data":jsonObj, // 请求体数据
"contentType":"application/x-www-form-urlencoded", //非默认值,不能省略
"dataType":"json", // 服务器端返回数据的解析方式
"success":function(response) { // 服务器处理请求成功,执行这个函数,响应体从参数这里传入
// response已经解析为JSON对象,可以直接使用“.属性名”方式访问具体属性
var result = response.result;
// 如果返回结果成功,显示成功(逻辑成功)
if("SUCCESS" == result) {
alert("SUCCESS");
}
// 如果返回结果失败,显示消息(逻辑失败)
if("FAILED" == result) {
var message = response.message;
alert(message);
}
},
"error":function(response){ // 处理请求失败,例如:404、500、400等等
alert(response);
}
});
});
});
2.3.2 EmployeeParam实体类
public class EmployeeParam {
private List<Employee> empList;
……
}
2.3.3 handler代码
@ResponseBody
@RequestMapping("/ajax/send/form/list/data")
public ResultEntity<String> receiveFormListData(EmployeeParam employeeParam) {
List<Employee> empList = employeeParam.getEmpList();
for (Employee employee : empList) {
System.out.println(employee);
}
return ResultEntity.successWithoutData();
}
2.4 小结
使用JSON格式作为请求体去传送数据,非常适合发送结构复杂的大对象数据。JSON格式和Java类型直接对应,不需要额外封装专门接收请求参数的实体类。
使用时需要注意的点:
- JSON对象需要使用JSON.stringify()转换为JSON字符串
- contentType要设置成application/json;charset=UTF-8
- handler方法接收使用Java类型形参前需要使用@RequestBody注解
3. 前端工程师和后端工程师对接
3.1 服务器端渲染
前端工程师先开发静态页面,开发完成后,交给后端工程师。
3.2 前后端分离
3.2.1 好处
-
更专注于自己擅长的领域
- 前端工程师不关心数据的获取方式
- 后端工程师不关心数据的展示方式
前端和后端可以并行开发
3.2.2 对接模式
-
项目启动时,前端工程师和后端工程师开会确定JSON数据的详细格式。
- 字段
- 类型
- 作用
- 必传
-
JSON格式确定后前后端分头开发
-
前端工程师在后端工程师未完成时使用Mock.js技术提供的假数据开发
-
- 后端开发完成将程序部署到测试服务器之后,前端工程师连接测试服务器使用真实数据测试,联调。
4. 准备工作
4.1 layer弹层组件
4.1.1 在工程中加入layer资源
4.1.2 在页面导入layer开发环境
考虑到每个页面都有可能用到,所以在include-head.jsp文件中引入
<script type="text/javascript" src="layer/layer.js"></script>
4.1.3 使用layer弹层提示
layer.msg("要提示的消息!");
4.2 Modal模态框
4.2.1 HTML标签
<script type="text/javascript">
$(function () {
$("#textModalBtn").click(function () {
$("#testModal").modal("show");
})
})
</script>
<div id="testModal" 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>
<div class="modal-body">
<p>模态框测试!!!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
4.2.2 所需环境
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
<script type="text/javascript" src="jquery/jquery-2.1.1.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
4.2.3 控制模态框
$('#myModal').modal('show'); // 打开模态框
$('#myModal').modal('hide'); // 隐藏模态框
5. 角色维护
5.1 创建模型
CREATE TABLE t_role (
`id` INT (11) NOT NULL auto_increment,
`name` VARCHAR (255),
PRIMARY KEY (id)
);
5.2 逆向工程
修改generatorConfig.xml
<table tableName="t_role" domainObjectName="Role" />
5.3 创建组件
5.4 前往角色维护页面
在include-sidebar.jsp修改角色维护超链接
<a href="role/to/page.html">
<i class="glyphicon glyphicon-king"></i> 角色维护
</a>
在spring-web-mvc.xml文件中配置view-controller
<mvc:view-controller path="/role/to/page.html" view-name="role-page"/>
创建/atcrowdfunding-admin-1-webui/src/main/webapp/WEB-INF/role-page.jsp
5.5 分页后端部分
5.5.1 SQL
SELECT
id,
`name`
FROM
t_role
WHERE
`name` LIKE CONCAT('%', '', '%')
5.5.2 RoleMapper.xml
<select id="selectForKeywordSearch" resultMap="BaseResultMap">
SELECT
id,
`name`
FROM
t_role
WHERE
`name` LIKE CONCAT('%', #{keyword}, '%')
</select>
5.5.3 RoleMapper接口方法
List<Role> selectForKeywordSearch(String keyword);
5.5.4 Service方法
@Override
public PageInfo<Role> queryForKeywordWithPage(
Integer pageNum,
Integer pageSize,
String keyword) {
// 1.开启分页功能
PageHelper.startPage(pageNum, pageSize);
// 2.执行查询
List<Role> list = roleMapper.selectForKeywordSearch(keyword);
// 3.封装为PageInfo对象
return new PageInfo<>(list);
}
5.5.5 handler方法
@ResponseBody
@RequestMapping("/role/search/by/keyword")
public ResultEntity<PageInfo<Role>> search(
@RequestParam(value="pageNum", defaultValue="1") Integer pageNum,
@RequestParam(value="pageSize", defaultValue="5") Integer pageSize,
@RequestParam(value="keyword", defaultValue="") String keyword
) {
// 1.查询得到PageInfo对象
PageInfo<Role> pageInfo = roleService.queryForKeywordWithPage(pageNum, pageSize, keyword);
// 2.封装结果对象返回
return ResultEntity.successWithData(pageInfo);
}
5.6 分页前端部分
5.6.1 创建外部JavaScript源码文件
所在工程:atcrowdfunding-admin-1-webui
<script type="text/javascript" src="script/my-role.js"></script>
5.6.2 页面初始化
$(function(){
// 调用分页参数初始化方法
initGlobalVariable();
// 执行分页
showPage();
});
5.6.3 initGlobalVariable(pageInfo)函数
// 初始化全局变量
function initGlobalVariable() {
window.pageSize = 5;
window.pageNum = 1;
window.keyword = "";
}
5.6.4 声明分页函数
※说明:之所以要单独声明一个函数,封装分页操作,是因为很多其他操作完成后都需要调用这个分页函数重新刷新页面上显示的数据等等。
为了简化实现过程,理顺思路,将分页函数拆分为三个函数逐步实现。
// 给服务器发送请求获取分页数据(pageInfo),并在页面上显示分页效果(主体、页码导航条)
function showPage() {
// 给服务器发送请求获取分页数据:PageInfo
var pageInfo = getPageInfo();
// 在页面上的表格中tbody标签内显示分页的主体数据
generateTableBody(pageInfo);
// 在页面上的表格中tfoot标签内显示分页的页码导航条
initPagination(pageInfo);
}
5.6.5 getPageInfo()函数
// 获取分页数据:PageInfo
function getPageInfo() {
// 以同步请求方式调用$.ajax()函数并获取返回值(返回值包含全部响应数据)
var ajaxResult = $.ajax({
"url":"role/search/by/keyword.json",
"type":"post",
"data":{
"pageNum":(window.pageNum == undefined)?1:window.pageNum,
"pageSize":(window.pageSize == undefined)?5:window.pageSize,
"keyword":(window.keyword == undefined)?"":window.keyword
},
"dataType":"json",
"async":false // 为了保证getPageInfo()函数能够在Ajax请求拿到响应后获取PageInfo,需要设置为同步操作
});
// 从全部响应数据中获取JSON格式的响应体数据
var resultEntity = ajaxResult.responseJSON;
// 从响应体数据中获取result,判断当前请求是否成功
var result = resultEntity.result;
// 如果成功获取PageInfo
if(result == "SUCCESS") {
return resultEntity.data;
}
if(result == "FAILED") {
layer.msg(resultEntity.message);
}
return null;
}
5.6.6 generateTableBody(pageInfo)函数
// 使用PageInfo数据在tbody标签内显示分页数据
function generateTableBody(pageInfo) {
// 执行所有操作前先清空
$("#roleTableBody").empty();
// 获取数据集合
var list = pageInfo.list;
// 判断list是否有效
if(list == null || list.length == 0) {
$("#roleTableBody")
.append("<tr><td colspan='4' style='text-align:center;'>没有查询到数据!</td></tr>");
return ;
}
for(var i = 0; i < list.length; i++) {
var role = list[i];
var checkBtn = "<button type='button' class='btn btn-success btn-xs'><i class=' glyphicon glyphicon-check'></i></button>";
var pencilBtn = "<button type='button' class='btn btn-primary btn-xs'><i class=' glyphicon glyphicon-pencil'></i></button>";
var removeBtn = "<button type='button' class='btn btn-danger btn-xs'><i class=' glyphicon glyphicon-remove'></i></button>";
var numberTd = "<td>"+(i+1)+"</td>";
var checkBoxTd = "<td><input roleid='"+role.id+"' type='checkbox'></td>";
var roleNameTd = "<td>"+role.name+"</td>";
var btnTd = "<td>"+checkBtn+" "+pencilBtn+" "+removeBtn+"</td>";
var tr = "<tr>"+numberTd+checkBoxTd+roleNameTd+btnTd+"</tr>";
// 将前面拼好的HTML代码追加到#roleTableBody中
$("#roleTableBody").append(tr);
}
}
5.6.7 initPagination()函数
修改/atcrowdfunding-admin-1-webui/src/main/webapp/WEB-INF/role-page.jsp文件
-
导入Pagination
<link rel="stylesheet" href="css/pagination.css" /> <script type="text/javascript" src="script/jquery.pagination.js"></script>
-
修改页面上的tfoot部分
<tfoot> <tr> <td colspan="6" align="center"> <div id="Pagination" class="pagination"> <!-- 这里显示分页 --> </div> </td> </tr> </tfoot>
-
my-role.js函数实现
//声明函数封装导航条初始化操作 function initPagination(pageInfo) { // 声明变量存储分页导航条显示时的属性设置 var paginationProperties = { num_edge_entries : 3, //边缘页数 num_display_entries : 5, //主体页数 callback : pageselectCallback, //回调函数 items_per_page : window.pageSize, //每页显示数据数量,就是pageSize current_page : (window.pageNum - 1),//当前页页码 prev_text : "上一页", //上一页文本 next_text : "下一页" //下一页文本 }; // 显示分页导航条 $("#Pagination").pagination(pageInfo.total, paginationProperties); }
5.6.8 pageselectCallback()函数
// 在每一次点击“上一页”、“下一页”、“页码”时执行这个函数跳转页面
function pageselectCallback(pageIndex,jq) {
// 将全局变量中的pageNum修改为最新值
// pageIndex从0开始,pageNum从1开始
window.pageNum = pageIndex + 1;
// 调用分页函数重新执行分页
showPage();
return false;
}