尚筹网-5.权限和Role的Ajax分页

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
    • 子角色2
      • 分组3
      • 分组4
  • 角色2
    • 子角色3
      • ……
    • 子角色4
      • ……
  • 角色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">&times;</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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容