尚筹网-6.Role维护和异步请求的异常映射

1 html和json扩展名问题

1.1 对应*.html扩展名的请求

返回一个完整页面的请求。

1.2 对应*.json扩展名的请求

返回JSON数据的请求。

{"result":"SUCCESS","message":"NO_MESSAGE","data":
{"pageNum":2,"pageSize":5,"size":5,"startRow":6,"endRow":10,"total":100,"pages":20,"list":
[{"id":6,"name":"role5"},{"id":7,"name":"role6"},{"id":8,"name":"role7"},{"id":9,"name":"role8"},
{"id":10,"name":"role9"}],"firstPage":1,"prePage":1,"nextPage":3,"lastPage":8,"isFirstPage":false,
"isLastPage":false,"hasPreviousPage":true,"hasNextPage":true,"navigatePages":8,"navigatepageNums":
[1,2,3,4,5,6,7,8]}}

1.3 匹配错误时的问题

如果请求扩展名是*.html,但是实际返回的响应体是JSON格式,Tomcat会认为实际返回的响应和响应消息头中的内容类型不匹配,会抛异常。

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

此时要解决这个问题,将返回JSON数据的请求扩展名设置为*.json或Tomcat无法识别的扩展名即可。

例如:xxx.json或xxx.rgh都可以。

2. 角色维护-关键词查询功能

2.1 基本思路

在点击“查询”按钮后,获取文本框中填写的keyword值,赋值给全局变量keyword,调用showPage()函数即可。

  • 给“查询”按钮标记id
  • 给“查询”按钮绑定单击响应函数
  • 在单击响应函数中获取文本框中输入的数据
  • 验证输入数据是否有效
    • 如果无效,提示,停止函数执行
    • 如果有效,赋值给window.keyword
  • 调用showPage()

2.2 代码

$("#searchBtn").click(function(){
    // 在单击响应函数中获取文本框中输入的数据
    var keyword = $.trim($("#keywordInput").val());
    // 验证输入数据是否有效
    if(keyword == null || keyword == "") {
        // 如果无效,提示,停止函数执行
        layer.msg("请输入关键词!");
        return ;
    }
    // 如果有效,赋值给window.keyword
    window.keyword = keyword;
    // 调用showPage()重新分页
    showPage();
});

layer.msg('xxx')无法显示的原因

3 角色维护-全选功能

3.1 功能在页面的位置

3.2 具体实现

3.2.1 标记

总checkbox:/atcrowdfunding-admin-1-webui/src/main/webapp/WEB-INF/role-page.jsp


    
        #
        
        名称
        操作
    

itembox:/atcrowdfunding-admin-1-webui/src/main/webapp/script/my-role.js

// 使用PageInfo数据在tbody标签内显示分页数据
function generateTableBody(pageInfo) {
    ……
    for(var i = 0; i < list.length; i++) {
        ……
        var checkBoxTd = "";
        ……
    }
}

3.2.2 给#summaryBox绑定单击响应函数

// 全选/全不选功能
$("#summaryBox").click(function(){
    // 1.获取当前checkbox的选中状态
    var currentStatus = this.checked;
    // 2.设置itemBox的选中状态
    $(".itemBox").prop("checked",currentStatus);
});

4. 角色维护-批量删除

4.1 准备工作

4.1.1 准备模态框


创建include-modal-role-confirm.jsp文件保存模态框内容。

在role-page.jsp中包含include-modal-role-confirm.jsp文件。

    ……
    <%@ include file="/WEB-INF/include-modal-role-confirm.jsp" %>

4.1.2 getRoleListByRoleIdArray()函数

// 根据roleIdArray查询roleList
function getRoleListByRoleIdArray(roleIdArray) {
    // 1.将roleIdArray转换成JSON字符串
    var requestBody = JSON.stringify(roleIdArray);  
    // 2.发送Ajax请求
    var ajaxResult = $.ajax({
        "url":"role/get/list/by/id/list.json",
        "type":"post",
        "data":requestBody,
        "contentType":"application/json;charset=UTF-8",
        "dataType":"json",
        "async":false
    });
    // 3.获取JSON对象类型的响应体
    var resultEntity = ajaxResult.responseJSON;
    // 4.验证是否成功
    var result = resultEntity.result;
    if(result == "SUCCESS") {   
        // 5.如果成功,则返回roleList
        return resultEntity.data;
    }
    if(result == "FAILED") {
        layer.msg(resultEntity.message);
        return null;
    }
    return null;    
}

对应的后端代码:

com.rgh.crowd.funding.handler.RoleHandler

// handler方法
@ResponseBody
@RequestMapping("/role/get/list/by/id/list")
public ResultEntity> getRoleListByIdList(@RequestBody List roleIdList) {
    List roleList = roleService.getRoleListByIdList(roleIdList);  
    return ResultEntity.successWithData(roleList);
}

com.rgh.crowd.funding.service.impl.RoleServiceImpl

// service方法
@Override
public List getRoleListByIdList(List roleIdList) {
    // 预期的SQL语句
    // select id,name from t_role where id in (1,2,3,6,12)
    // 创建实体类Role对应的Example对象
    RoleExample roleExample = new RoleExample();
    // 在Example对象中封装查询条件
    roleExample.createCriteria().andIdIn(roleIdList);
    // 执行查询
    return roleMapper.selectByExample(roleExample);
}

4.1.3 showRemoveConfirmModal()函数

// 打开删除确认模态框
function showRemoveConfirmModal() {
    // 1.将模态框显示出来
    $("#confirmModal").modal("show");
    // 2.根据roleIdList获取roleList
    var roleList = getRoleListByRoleIdArray(window.roleIdArray);
    // 3.清空#confirmModalTableBody
    $("#confirmModalTableBody").empty();
    // 4.填充#confirmModalTableBody
    for(var i = 0; i < roleList.length; i++) {
        // 5.获取角色相关数据
        var role = roleList[i];
        var id = role.id;
        var name = role.name;
        var trHTML = ""+id+""+name+"";   
        // 6.执行填充
        $("#confirmModalTableBody").append(trHTML);
    }   
}

4.2 给批量删除按钮绑定单击响应函数

4.2.1 标记批量删除按钮


4.2.2 检查itemBox是否被选中

// 给批量删除按钮绑定单击响应函数
$("#batchRemoveBtn").click(function(){
    // 获取被选中的itemBox数组长度
    var length = $(".itemBox:checked").length;
    // 如果长度为0,说明没有选择itemBox
    if(length == 0) {
        layer.msg("请至少选择一条!");
        return ;
    }
    // 未完待续...
});

4.2.3 在弹出的模态框中显示confirm信息

// 给批量删除按钮绑定单击响应函数
$("#batchRemoveBtn").click(function(){
    // 获取被选中的itemBox数组长度
    var length = $(".itemBox:checked").length;
    // 如果长度为0,说明没有选择itemBox
    if(length == 0) {
        layer.msg("请至少选择一条!");
        return ;
    }
    // 在全局作用域内创建roleIdArray
    window.roleIdArray = new Array();
    // 遍历$(".itemBox:checked")
    $(".itemBox:checked").each(function(){
        // 通过checkbox的roleid属性获取roleId值
        var roleId = $(this).attr("roleid");
        // 存入数组
        window.roleIdArray.push(roleId);
    });
    // 调用函数打开模态框
    showRemoveConfirmModal();
});

4.3 点击模态框的OK按钮执行删除

4.3.1 标记OK按钮


4.3.2 绑定单击响应函数

// 给确认模态框中的OK按钮绑定单击响应函数
$("#confirmModalBtn").click(function(){
    var requestBody = JSON.stringify(window.roleIdArray);   
    $.ajax({
        "url":"role/batch/remove.json",
        "type":"post",
        "data":requestBody,
        "contentType":"application/json;charset=UTF-8",
        "dataType":"json",
        "success":function(response){
            var result = response.result;
            if(result == "SUCCESS") {
                layer.msg("操作成功!");         
                // 如果删除成功,则重新调用分页方法
                showPage();
            }
            if(result == "FAILED") {
                layer.msg(response.message);
            }
            // 不管成功还是失败,都需要关掉模态框
            $("#confirmModal").modal("hide");   
        },
        "error":function(response){
            layer.msg(response.message);
        }
    });
});

4.3.3 后端代码

com.rgh.crowd.funding.handler.RoleHandler

@ResponseBody
@RequestMapping("/role/batch/remove")
public ResultEntity batchRemove(@RequestBody List roleIdList) {
    roleService.batchRemove(roleIdList);    
    return ResultEntity.successWithoutData();
}

com.rgh.crowd.funding.service.impl.RoleServiceImpl

@Override
public void batchRemove(List roleIdList) {
    RoleExample roleExample = new RoleExample();
    roleExample.createCriteria().andIdIn(roleIdList);   
    roleMapper.deleteByExample(roleExample);
}

5 角色维护-单条删除

// 针对.removeBtn这样动态生成的元素对象使用on()函数方式绑定单击响应函数
// $("动态元素所依附的静态元素").on("事件类型","具体要绑定事件的动态元素的选择器", 事件响应函数);
$("#roleTableBody").on("click",".removeBtn", function(){
    // 获取当前记录的roleId
    var roleId = $(this).attr("roleId");
    // 存入全局变量数组
    window.roleIdArray = new Array();
    window.roleIdArray.push(roleId);
    // 打开模态框(后续所有操作都和批量删除一样)
    showRemoveConfirmModal();
});

6 角色维护-新增

6.1 大体步骤

  • 给“新增”按钮绑定单击响应函数
  • 打开模态框
  • 给“保存”按钮绑定单击响应函数
    • 收集文本框内容
    • 发送请求
  • 请求处理完成关闭模态框、重新分页、清理表单

6.2 给“新增”按钮绑定单击响应函数

6.2.1 标记“新增”按钮


6.2.2 绑定单击响应函数

$("#addBtn").click(function(){
    alert("aaa...");
});

6.3 打开模态框

6.3.1 准备模态框

先准备模态框的HTML代码


创建/atcrowdfunding-admin-1-webui/src/main/webapp/WEB-INF/include-modal-role-add.jsp文件

将include-modal-role-add.jsp包含到role-page.jsp

<%@ include file="/WEB-INF/include-modal-role-add.jsp" %>

6.3.2 打开模态框

$("#addBtn").click(function(){
    $("#addModal").modal("show");
});

6.4 给“保存”按钮绑定单击响应函数

6.4.1 标记“保存”按钮


6.4.2 绑定单击响应函数

$("#addModalBtn").click(function(){
    // 1.收集文本框内容
    var roleName = $.trim($("#roleNameInput").val());
    if(roleName == null || roleName == "") {
        layer.msg("请输入有效角色名称!");
        return ;
    }
    // 2.发送请求
    $.ajax({
        "url":"role/save/role.json",
        "type":"post",
        "data":{
            "roleName":roleName
        },
        "dataType":"json",
        "success":function(response){
            var result = response.result;
            if(result == "SUCCESS") {
                layer.msg("操作成功!");
                // 3.操作成功重新分页
                // 前往最后一页
                window.pageNum = 999999;
                showPage();
            }
            if(result == "FAILED") {
                layer.msg(response.message);
            }
            // 4.不管成功还是失败,关闭模态框
            $("#addModal").modal("hide");
            // 5.清理本次在文本框填写的数据
            $("#roleNameInput").val("");
        },
        "error":function(response){
            layer.msg(response.message);
        }
    });
});

6.5 后端代码

com.rgh.crowd.funding.handler.RoleHandler

@ResponseBody
@RequestMapping("/role/save/role")
public ResultEntity saveRole(@RequestParam("roleName") String roleName) {   
    roleService.saveRole(roleName);
    return ResultEntity.successWithoutData();
}

com.rgh.crowd.funding.service.impl.RoleServiceImpl

@Override
public void saveRole(String roleName) {
    roleMapper.insert(new Role(null, roleName));
}

7 角色维护-更新

7.1 大体步骤

  • 给“铅笔”按钮绑定单击响应函数

    因为“铅笔”按钮是动态生成的,所以需要使用on()方式

  • 打开模态框

    • 准备模态框
    • 把roleId保存到全局变量
    • 获取到当前“铅笔”按钮所在行的roleName
    • 使用roleName回显模态框中的表单
  • 给“更新”按钮绑定单击响应函数

    • 收集文本框内容
    • 发送请求
    • 请求处理完成关闭模态框、重新分页

7.2 给“铅笔”按钮绑定单击响应函数

7.2.1 标记“铅笔”按钮

找到/atcrowdfunding-admin-1-webui/src/main/webapp/script/my-role.js文件

function generateTableBody(pageInfo) {
    ……
    for(var i = 0; i < list.length; i++) {
        ……
        var pencilBtn = "";       
        ……
    }
}

7.2.2 准备模态框


<%@ include file="/WEB-INF/include-modal-role-edit.jsp" %>

7.2.3 绑定单击响应函数

$("#roleTableBody").on("click",".editBtn",function(){
    // 1.获取当前按钮的roleId
    window.roleId = $(this).attr("roleId");
    // 2.获取当前按钮所在行的roleName
    var roleName = $(this).parents("tr").children("td:eq(2)").text();
    // 3.修改模态框中文本框的value值,目的是在显示roleName
    $("#roleNameInputEdit").val(roleName);
    // 4.打开模态框
    $("#editModal").modal("show");
});

7.3 给“更新”按钮绑定单击响应函数

7.3.1 前端代码

$("#editModalBtn").click(function(){
    // 1.获取文本框值
    var roleName = $.trim($("#roleNameInputEdit").val());
    if(roleName == null || roleName == "") {
        layer.msg("请输入有效角色名称!");
        return ;
    }
    // 2.发送请求
    $.ajax({
        "url":"role/update/role.json",
        "type":"post",
        "data":{
            "id":window.roleId,
            "name":roleName
        },
        "dataType":"json",
        "success":function(response){
            var result = response.result;
            if(result == "SUCCESS") {
                layer.msg("操作成功!");
                // 3.操作成功重新分页
                showPage();
            }
            if(result == "FAILED") {
                layer.msg(response.message);
            }
            // 4.不管成功还是失败,关闭模态框
            $("#editModal").modal("hide");
        }
    });
});

7.3.2 后端代码

@ResponseBody
@RequestMapping("/role/update/role")
public ResultEntity updateRole(Role role) {
    roleService.updateRole(role);   
    return ResultEntity.successWithoutData();
}
@Override
public void updateRole(Role role) {
    roleMapper.updateByPrimaryKey(role);
}

8 @RestController

相当于@Controller注解+@ResponseBody注解,类使用了@RestController之后相当于在每一个方法上都加了@ResponseBody注解。

9 异常映射兼容异步请求

9.1 问题表现

Ajax请求在服务器端处理过程中抛出异常,经过异常处理器:

com.rgh.crowd.funding.exception.CrowFundingExceptionResolever

@ControllerAdvice
public class CrowdFundingExceptionResolever {
    @ExceptionHandler(value=Exception.class)
    public ModelAndView catchException(Exception exception) {
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", exception);
        mav.setViewName("system-error");    
        return mav;
    }
}

目前这个异常处理机制,只能返回页面,而不能针对Ajax请求返回JSON格式的响应数据。所以Ajax请求处理过程中,如果抛出异常,返回异常信息页面,Ajax程序无法正常解析,导致页面不能正常显示和工作,也不能给出友好的错误提示。

9.2 问题解决思路

9.3 异步请求特点

9.4 分辨异步请求的工具方法

在atcrowdfunding-admin-3-common工程加入servlet-api依赖


    javax.servlet
    servlet-api
    provided

com.rgh.crowd.funding.util.CrowdFundingUtils

/**
 * 用于判断一个请求是否是异步请求
 * @param request
 * @return
 */
public static boolean checkAsyncRequest(HttpServletRequest request) {
    // 1.获取相应请求消息头
    String accept = request.getHeader("Accept");
    String xRequested = request.getHeader("X-Requested-With");
    // 2.判断请求消息头数据中是否包含目标特征
    if(
        (stringEffective(accept) && accept.contains("application/json")) 
        || 
        (stringEffective(xRequested) && xRequested.contains("XMLHttpRequest")) ) {
        return true;
    }   
    return false;
}

9.5 升级后的异常处理器

@ControllerAdvice
public class CrowdFundingExceptionResolever {
    @ExceptionHandler(value=Exception.class)
    public ModelAndView catchException(
            Exception exception, 
            HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        // 1.对当前请求进行检查
        boolean checkAsyncRequestResult = CrowdFundingUtils.checkAsyncRequest(request);
        // 2.如果是异步请求
        if(checkAsyncRequestResult) {
            // 3.创建ResultEntity对象
            ResultEntity resultEntity = ResultEntity.failed(ResultEntity.NO_DATA, exception.getMessage());
            // 4.将resultEntity转换为JSON格式
            Gson gson = new Gson();
            String json = gson.toJson(resultEntity);
            // 5.将json作为响应数据返回给浏览器
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(json);   
            return null;
        }
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", exception);
        mav.setViewName("system-error");    
        return mav;
    }
}

※需要Gson支持


    com.google.code.gson
    gson
    2.8.5

9.6 改进提示消息

  • 所在工程:atcrowdfunding-admin-3-common
  • 全类名:com.rgh.crowd.funding.util.CrowdFundingConstant
public static final Map EXCEPTION_MESSAGE_MAP = new HashMap<>();
static {
    EXCEPTION_MESSAGE_MAP.put("java.lang.ArithmeticException", "系统在进行数学运算时发生错误");
    EXCEPTION_MESSAGE_MAP.put("java.lang.RuntimeException", "系统在运行时发生错误");
    EXCEPTION_MESSAGE_MAP.put("com.rgh.crowd.funding.exception.LoginException", "登录过程中运行错误");
}
  • 所在工程:atcrowdfunding-admin-2-component
  • 全类名:com.rgh.crowd.funding.exeption.CrowdFundingExceptionResolever
@ControllerAdvice
public class CrowdFundingExceptionResolever {
    @ExceptionHandler(value=Exception.class)
    public ModelAndView catchException(
        Exception exception, 
        HttpServletRequest request,
        HttpServletResponse response) throws IOException {
        // 1.对当前请求进行检查
        boolean checkAsyncRequestResult = CrowdFundingUtils.checkAsyncRequest(request);
        // 2.如果是异步请求
        if(checkAsyncRequestResult) {
            // 根据异常类型在常量中的映射,使用比较友好的文字显示错误提示消息
            String exceptionClassName = exception.getClass().getName();
            String message = CrowdFundingConstant.EXCEPTION_MESSAGE_MAP.get(exceptionClassName);
            if(message == null) {
                message = "系统未知错误";
            }
            // 3.创建ResultEntity对象
            ResultEntity resultEntity = ResultEntity.failed(ResultEntity.NO_DATA, message);
            // 4.将resultEntity转换为JSON格式
            Gson gson = new Gson();
            String json = gson.toJson(resultEntity);
            // 5.将json作为响应数据返回给浏览器
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(json);   
            return null;
        }
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", exception);
        mav.setViewName("system-error");    
        return mav;
    }
}

10 登录拦截器兼容异步请求

问题的产生和解决的思路都和异常映射部分一致

com.rgh.crowd.funding.interceptor.LoginInterceptor

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    // 通过request对象获取HttpSession对象
    HttpSession session = request.getSession(); 
    // 从Session域尝试获取已登录用户对象
    Admin admin = (Admin) session.getAttribute(CrowdFundingConstant.ATTR_NAME_LOGIN_ADMIN); 
    // 如果没有获取到Admin对象
    if(admin == null) {     
        // 进一步判断当前请求是否是异步请求
        boolean checkAsyncRequestResult = CrowdFundingUtils.checkAsyncRequest(request);
        if(checkAsyncRequestResult) {           
            // 为异步请求的响应创建ResultEntity对象
            ResultEntity resultEntity = ResultEntity.failed(ResultEntity.NO_DATA, CrowdFundingConstant.MESSAGE_ACCESS_DENIED);          
            // 创建Gson对象
            Gson gson = new Gson();         
            // 将ResultEntity对象转换为JSON字符串
            String json = gson.toJson(resultEntity);            
            // 设置响应的内容类型
            response.setContentType("application/json;charset=UTF-8");
            // 将JSON字符串作为响应数据返回
            response.getWriter().write(json);
            // 表示不能放行,后续操作不执行
            return false;   
        }
        // 将提示消息存入request域
        request.setAttribute(CrowdFundingConstant.ATTR_NAME_MESSAGE, CrowdFundingConstant.MESSAGE_ACCESS_DENIED);
        // 转发到登录页面
        request.getRequestDispatcher("/WEB-INF/admin-login.jsp").forward(request, response);    
        return false;
    }   
    // 如果admin对象有效,则放行继续执行后续操作
    return true;
}

11 分页的showPage()函数修正

// 给服务器发送请求获取分页数据(pageInfo),并在页面上显示分页效果(主体、页码导航条)
function showPage() {   
    // 给服务器发送请求获取分页数据:PageInfo
    var pageInfo = getPageInfo();
    console.log(pageInfo);  
    if(pageInfo == null) {
        // 如果没有获取到pageInfo数据,则停止后续操作
        return ;
    }   
    // 在页面上的表格中tbody标签内显示分页的主体数据
    generateTableBody(pageInfo);    
    // 在页面上的表格中tfoot标签内显示分页的页码导航条
    initPagination(pageInfo);
}

你可能感兴趣的:(尚筹网-6.Role维护和异步请求的异常映射)