1.管理员登录

1.目标

识别操作系统的人的身份,控制该身份的行为。

2.思路

二.管理员功能 - 图1

3.需要做的事情

需要做的:

  1. 对存入数据库的密码进行MD5加密
  2. 在登录界面登录失败时的处理
  3. 抽取后台页面的公共部分
  4. 检查登录状态,防止未登录时访问受保护资源的情况

    4.代码

    1.创建工具方法进行MD5加密

    1.项目结构

    image.png

    2.MD5加密的工具方法

    在上面的类中加入一个方法
    1. public class CrowdUtil {
    2. /**
    3. * 此方法是用于给字符串进行md5加密的工具方法
    4. * @return 进行md5加密后的结果
    5. * @param source 传入要加密的内容
    6. */
    7. public static String md5(String source){
    8. // 1.判断source是否有效
    9. if (source == null || source.length() == 0) {
    10. //如果传入的加密内容为空或是空字符串,抛出LoginFailedException
    11. // 2.如果不是有效的字符串抛出异常
    12. throw new LoginFailedException(CrowdConstant.MESSAGE_STRING_INVALIDATE);
    13. }
    14. try {
    15. //表示算法名
    16. String algorithm = "md5";
    17. // 3.得到MessageDigest对象,设置加密方式为md5
    18. MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
    19. // 4.将获得的明文字符串转换为字节数组
    20. byte[] input = source.getBytes();
    21. // 5.对转换得到的字节数组进行md5加密
    22. byte[] output = messageDigest.digest(input);
    23. //设置BigInteger的signum
    24. //signum : -1表示负数、0表示零、1表示正数
    25. int signum = 1;
    26. // 6.将字节数组转换成Big Integer
    27. BigInteger bigInteger = new BigInteger(signum,output);
    28. // 7.设置将bigInteger的值按照16进制转换成字符串,最后全部转换成大写,得到最后的加密结果
    29. int radix = 16;
    30. String encoded = bigInteger.toString(radix).toUpperCase();
    31. // 8.返回加密后的字符串
    32. return encoded;
    33. } catch (NoSuchAlgorithmException e) {
    34. e.printStackTrace();
    35. }
    36. // 9.触发异常则返回null
    37. return null;
    38. }
    39. }

    2.自定义异常

    1.项目结构

    image.png

    2.登录失败后抛出的自定义异常(代码)

    ```java package com.zh.crowd.exception;

/**

  • 登录失败后抛出的异常 */ public class LoginFailedException extends RuntimeException {

    public LoginFailedException() {

    1. super();

    }

    public LoginFailedException(String message) {

     super(message);
    

    }

    public LoginFailedException(String message, Throwable cause) {

     super(message, cause);
    

    }

    public LoginFailedException(Throwable cause) {

     super(cause);
    

    }

    protected LoginFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {

     super(message, cause, enableSuppression, writableStackTrace);
    

    } }

<a name="xxfFM"></a>
#### 3.在异常处理器类中增加登录失败异常的处理

1. 项目结构

![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641273999310-9fd4b088-8bcf-4d3b-bb3f-7ec4e5fb1a8c.png#clientId=ud2901e48-47d3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=280&id=u884b1c1f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=559&originWidth=701&originalType=binary&ratio=1&rotation=0&showTitle=false&size=28194&status=done&style=none&taskId=udb8468f8-210f-441f-bba7-f5e6c5ab7a3&title=&width=350.5)

2. 增加代码(增加了一个方法)
```java
 // 触发登录失败异常,则继续返回登陆页面
    @ExceptionHandler(value = LoginFailedException.class)
    public ModelAndView resolverLoginFailedException(
            LoginFailedException exception, HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        String viewName = "admin-login";
        return commonCode(exception,request,response,viewName);
    }
  1. 在登录页面显示异常消息(项目结构)

image.png

  1. 在登录页面显示异常消息(代码)
    <p>${requestScope.exection.message}</p>
         <div class="form-group has-success has-feedback">
             <input type="text" name="login-user" class="form-control" id="inputSuccess4" placeholder="请输入登录账号" autofocus>
             <span class="glyphicon glyphicon-user form-control-feedback"></span>
         </div>
    

    3.handle方法

    1.项目结构

    image.png

    2.AdminHandler代码

    ```java package com.zh.crowd.mvc.handler;

import com.zh.crowd.constant.CrowdConstant; import com.zh.crowd.entity.Admin; import com.zh.crowd.mapper.AdminMapper; import com.zh.crowd.service.api.AdminService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

@Controller public class AdminHandler {

@Autowired
private AdminService adminService;

@RequestMapping("/admin/do/login.html")
public String doLogin(
        @RequestParam("loginAcct") String loginAcct,
        @RequestParam("userPswd")String userPswd,
        HttpSession session){
    // 调用Service方法执行登录检查
    // 这个方法如果能返回Admin对象说明登录成功,如果账号、密码不正确则会抛出异常
   Admin admin =  adminService.getAdminByLoginAcct(loginAcct,userPswd);
   //将登录成功的admin对象存入session域
    session.setAttribute(CrowdConstant.LOGIN_ADMIN_NAME,admin);
    return "admin-main";

}

}

<a name="RETqM"></a>
### 4.service方法
<a name="uheGI"></a>
#### 1.项目结构
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641282390809-54cfc0ba-8d3a-4da0-9e26-93e0f64091b8.png#clientId=ud2901e48-47d3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=278&id=u14bd362f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=555&originWidth=537&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26893&status=done&style=none&taskId=u6b983b32-afcc-4918-90d5-599b5a3ee3e&title=&width=268.5)
<a name="nWc2X"></a>
#### 2.AdminServiceImpl代码
```java
@Override
    public Admin getAdminByLoginAcct(String loginAcct, String userPswd) {
        // 1.根据登录账号查询Admin对象
        // ①创建AdminExample对象
        AdminExample example = new AdminExample();
        // ②创建Criteria对象
        Criteria criteria = example.createCriteria();
        // ③在Criteria对象封装查询条件
        criteria.andLoginAcctEqualTo(loginAcct);
        // ④调用adminMapper执行查询
        List<Admin> list = adminMapper.selectByExample(example);
        // 2.判断Admin是否为null
        if (list == null || list.size() == 0){
            throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
        }
        if (list.size() > 1){
            throw new RuntimeException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
        }
        Admin admin = list.get(0);
        // 3.如果Admin对象为null,则抛出异常
        if (admin == null){
            throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
        }
        // 4.如果Admin对象不为null,则将数据库密码从Admin对象中取出
        String adminUserPswdDB = admin.getUserPswd();
        // 5.将表单提交的明文密码进行加密
        String md5 = CrowdUtil.md5(userPswd);
        // 6.对密码进行比较
        if (!Objects.equals(adminUserPswdDB,md5)){
            // 7.如果比较结果是不一致则抛出异常
            throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
        }
        // 8.如果一致则返回Admin对象
        return admin;
    }

5.使用重定向(修改handler代码)

1.修改AdminHandler.java代码

@RequestMapping("/admin/do/login.html")
    public String doLogin(
            @RequestParam("loginAcct") String loginAcct,
            @RequestParam("userPswd")String userPswd,
            HttpSession session){
        // 调用Service方法执行登录检查
        // 这个方法如果能返回Admin对象说明登录成功,如果账号、密码不正确则会抛出异常
       Admin admin =  adminService.getAdminByLoginAcct(loginAcct,userPswd);
       //将登录成功的admin对象存入session域
        session.setAttribute(CrowdConstant.LOGIN_ADMIN_NAME,admin);
        //重定向到登录完成后的主页面(重定向防止重复提交表单,增加不必要的数据库访问)
        return "redirect:/admin/to/main/page.html";

    }

2.修改spring-web-mvc.xml文件

重定向去的页面,因为数据放在session中,因此直接通过mvc:view-controller指定该页面
增加代码

<mvc:view-controller path="/admin/to/main/page.html" view-name="admin-main"/>

3.显示已经登录的账号的昵称

修改admin-main.jsp页面

<i class="glyphicon glyphicon-user">${sessionScope.loginAdmin.userName}</i>

6.抽取后台公共页面

对admin-main.jsp页面进行抽取
JSP页面中,对公共的页面,可以在一个单独页面中保存,在需要调用这些公共页面的时候通过<%@include file=”…”%>引入这些页面。
注意:如果在引入时,发现引入的地方原本的中文显示乱码,可以通过给这些公共的页面代码加上一句<%@page pageEncoding=”UTF-8”%>

1.抽取include-head.jsp

<%@page pageEncoding="UTF-8"%>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/font-awesome.min.css">
    <link rel="stylesheet" href="css/main.css">
    <style>
        .tree li {
            list-style-type: none;
            cursor:pointer;
        }
        .tree-closed {
            height : 40px;
        }
        .tree-expanded {
            height : auto;
        }
    </style>
    <script type="text/javascript" src="jquery/jquery-2.1.1.min.js"></script>
    <script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="script/docs.min.js"></script>
    <script type="text/javascript" src="layer/layer.js"></script>
    <script type="text/javascript">
        $(function () {
            $(".list-group-item").click(function(){
                if ( $(this).find("ul") ) {
                    $(this).toggleClass("tree-closed");
                    if ( $(this).hasClass("tree-closed") ) {
                        $("ul", this).hide("fast");
                    } else {
                        $("ul", this).show("fast");
                    }
                }
            });
        });
    </script>
</head>

2.抽取include-nav.jsp

<%--在被引入时显示乱码,就是用下面这句设置pageEncoding--%>
<%@page pageEncoding="UTF-8"%>
<%--引入security标签库--%>
<%--<%@taglib prefix="security" uri="http://www.springframework.org/security/tags" %>--%>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container-fluid">
        <div class="navbar-header">
            <div><a class="navbar-brand" style="font-size:32px;" href="#">众筹平台 - 控制面板</a></div>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <ul class="nav navbar-nav navbar-right">
                <li style="padding-top:8px;">
                    <div class="btn-group">
                        <button type="button" class="btn btn-default btn-success dropdown-toggle" data-toggle="dropdown">
                            <%-- 通过principal.originalAdmin.userName得到当前用户的昵称(principal其实就是前面返回的SecurityAdmin对象) --%>
<%--                            <i class="glyphicon glyphicon-user"><security:authentication property="principal.originalAdmin.userName"/></i>--%>
                                <i class="glyphicon glyphicon-user">${sessionScope.loginAdmin.userName}</i>
                            <span class="caret"></span>
                        </button>
                        <ul class="dropdown-menu" role="menu">
                            <li><a href="#"><i class="glyphicon glyphicon-cog"></i> 个人设置</a></li>
                            <li><a href="#"><i class="glyphicon glyphicon-comment"></i> 消息</a></li>
                            <li class="divider"></li>
<%--                            <li><a href="security/do/logout.html"><i class="glyphicon glyphicon-off"></i> 退出系统</a></li>--%>
                            <li><a href="admin/do/logout.html"><i class="glyphicon glyphicon-off"></i> 退出系统</a></li>
                        </ul>
                    </div>
                </li>
                <li style="margin-left:10px;padding-top:8px;">
                    <button type="button" class="btn btn-default btn-danger">
                        <span class="glyphicon glyphicon-question-sign"></span> 帮助
                    </button>
                </li>
            </ul>
            <form class="navbar-form navbar-right">
                <input type="text" class="form-control" placeholder="查询">
            </form>
        </div>
    </div>
</nav>

3.抽取include-sidebar.jsp

<%--在被引入时显示乱码,就是用下面这句设置pageEncoding--%>
<%@page pageEncoding="UTF-8"%>
<div class="col-sm-3 col-md-2 sidebar">
    <div class="tree">
        <ul style="padding-left:0px;" class="list-group">
            <li class="list-group-item tree-closed" >
                <a href="main.html"><i class="glyphicon glyphicon-dashboard"></i> 控制面板</a>
            </li>
            <li class="list-group-item tree-closed">
                <span><i class="glyphicon glyphicon glyphicon-tasks"></i> 权限管理 <span class="badge" style="float:right">3</span></span>
                <ul style="margin-top:10px;display:none;">
                    <li style="height:30px;">
                        <a href="admin/page/page.html"><i class="glyphicon glyphicon-user"></i> 用户维护</a>
                    </li>
                    <li style="height:30px;">
                        <a href="role/to/page.html"><i class="glyphicon glyphicon-king"></i> 角色维护</a>
                    </li>
                    <li style="height:30px;">
                        <a href="menu/to/page.html"><i class="glyphicon glyphicon-lock"></i> 菜单维护</a>
                    </li>
                </ul>
            </li>
            <li class="list-group-item tree-closed">
                <span><i class="glyphicon glyphicon-ok"></i> 业务审核 <span class="badge" style="float:right">3</span></span>
                <ul style="margin-top:10px;display:none;">
                    <li style="height:30px;">
                        <a href="auth_cert.html"><i class="glyphicon glyphicon-check"></i> 实名认证审核</a>
                    </li>
                    <li style="height:30px;">
                        <a href="auth_adv.html"><i class="glyphicon glyphicon-check"></i> 广告审核</a>
                    </li>
                    <li style="height:30px;">
                        <a href="auth_project.html"><i class="glyphicon glyphicon-check"></i> 项目审核</a>
                    </li>
                </ul>
            </li>
            <li class="list-group-item tree-closed">
                <span><i class="glyphicon glyphicon-th-large"></i> 业务管理 <span class="badge" style="float:right">7</span></span>
                <ul style="margin-top:10px;display:none;">
                    <li style="height:30px;">
                        <a href="cert.html"><i class="glyphicon glyphicon-picture"></i> 资质维护</a>
                    </li>
                    <li style="height:30px;">
                        <a href="type.html"><i class="glyphicon glyphicon-equalizer"></i> 分类管理</a>
                    </li>
                    <li style="height:30px;">
                        <a href="process.html"><i class="glyphicon glyphicon-random"></i> 流程管理</a>
                    </li>
                    <li style="height:30px;">
                        <a href="advertisement.html"><i class="glyphicon glyphicon-hdd"></i> 广告管理</a>
                    </li>
                    <li style="height:30px;">
                        <a href="message.html"><i class="glyphicon glyphicon-comment"></i> 消息模板</a>
                    </li>
                    <li style="height:30px;">
                        <a href="project_type.html"><i class="glyphicon glyphicon-list"></i> 项目分类</a>
                    </li>
                    <li style="height:30px;">
                        <a href="tag.html"><i class="glyphicon glyphicon-tags"></i> 项目标签</a>
                    </li>
                </ul>
            </li>
            <li class="list-group-item tree-closed" >
                <a href="param.html"><i class="glyphicon glyphicon-list-alt"></i> 参数管理</a>
            </li>
        </ul>
    </div>
</div>

4.抽取后的admin-main.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="zh-CN">
<%@ include file="/WEB-INF/include-head.jsp"%>
<body>
<%@ include file="/WEB-INF/include-nav.jsp"%>
<div class="container-fluid">
    <div class="row">
        <%@include file="/WEB-INF/include-sidebar.jsp"%>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
            <h1 class="page-header">控制面板</h1>
            <div class="row placeholders">
<%--                <security:authorize access="hasRole('经理')">--%>
                    <div class="col-xs-6 col-sm-3 placeholder">
                        <img data-src="holder.js/200x200/auto/sky" class="img-responsive" alt="Generic placeholder thumbnail">
                        <h4>Label</h4>
                        <span class="text-muted">Something else</span>
                    </div>
<%--                </security:authorize>--%>
<%--                <security:authorize access="hasAuthority('role:delete')">--%>
                    <div class="col-xs-6 col-sm-3 placeholder">
                        <img data-src="holder.js/200x200/auto/vine" class="img-responsive" alt="Generic placeholder thumbnail">
                        <h4>Label</h4>
                        <span class="text-muted">Something else</span>
                    </div>
<%--                </security:authorize>--%>
                <div class="col-xs-6 col-sm-3 placeholder">
                    <img data-src="holder.js/200x200/auto/sky" class="img-responsive" alt="Generic placeholder thumbnail">
                    <h4>Label</h4>
                    <span class="text-muted">Something else</span>
                </div>
                <div class="col-xs-6 col-sm-3 placeholder">
                    <img data-src="holder.js/200x200/auto/vine" class="img-responsive" alt="Generic placeholder thumbnail">
                    <h4>Label</h4>
                    <span class="text-muted">Something else</span>
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>

2.登录状态检查

1.目标

将部分资源保护起来,让没有登录的请求不能访问。

2.思路

image.png

3.代码

主要是通过一个异常配合一个拦截器来实现。

1.创建自定义异常

①首先创建一个自定义异常AccessForbiddenException,在用户未登录时访问受保护资源时抛出:

1.项目结构

image.png

2.自定义异常AccessForbiddenException(代码)

package com.zh.crowd.exception;

// 未登录时访问受保护资源时抛出的异常
public class AccessForbiddenException extends RuntimeException{

    public AccessForbiddenException() {
        super();
    }

    public AccessForbiddenException(String message) {
        super(message);
    }

    public AccessForbiddenException(String message, Throwable cause) {
        super(message, cause);
    }

    public AccessForbiddenException(Throwable cause) {
        super(cause);
    }

    protected AccessForbiddenException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

2.创建拦截器类

创建一个拦截器,拦截除了登录页面、登录请求、登出操作的其他请求,只有能从session域中得到admin对象时,才可以放行:

1.项目结构

image.png

2.拦截器类(代码)

// 拦截器类,用来在未登录时访问受保护页面时进行拦截并抛出AccessForbiddenException
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.通过request获得session对象
        HttpSession session = request.getSession();
        // 2.尝试从session域中取出Admin对象
        Admin admin = (Admin) session.getAttribute(CrowdConstant.LOGIN_ADMIN_NAME);
        // 3.判断admin对象是否为空,若为空表示未登录,抛出异常
        if (admin == null){
            // 4.抛出异常
            throw new AccessForbiddenException(CrowdConstant.MESSAGE_ACCESS_FORBIDDEN);
        }
        // 5.admin对象不为空,表示已登录,放行
        return true;
    }
}

3.注册拦截器类

在spring-web-mvc.xml中添加注册拦截器类

<!--在mvc容器中注册拦截器 : 在使用SpringSecurity后,就要注释掉原来的自定义的拦截器了-->
    <mvc:interceptors>
    <!--    mvc:mapping配置要拦截的资源-->
    <!--    【/*】对应一层路径 比如:/aa  -->
    <!--    【/**】对应多层路径 比如:/aa/bb或者/aa/bb/cc或者/aa/bb/cc/dd -->
          <mvc:interceptor>
              <mvc:mapping path="/**"/>
    <!--    mvc:exclude-mapping配置不要拦截的资源-->
                <mvc:exclude-mapping path="/admin/to/login/page.html"/>
                <mvc:exclude-mapping path="/admin/do/logout.html"/>
                <mvc:exclude-mapping path="/admin/do/login.html"/>
<!--              配置拦截器类-->
                <bean class="com.zh.crowd.mvc.interceptor.LoginInterceptor"/>
   </mvc:interceptor>
  </mvc:interceptors>

3.管理员维护

1.任务清单

  1. 分页显示Admin数据
    1. 不带关键字分页
    2. 带关键字分页
  2. 新增Admin
  3. 修改Admin
  4. 单条删除Admin

    2.分页

    1.目标

    将数据库中的Admin数据在页面上以分页的形式进行显示。

    2.思路

    image.png

    3.代码

    1.引入PageHelper

  5. 项目结构

image.png

  1. 确认在上图pom.xml中有如下依赖

    <!-- MyBatis 分页插件 -->
             <dependency>
                 <groupId>com.github.pagehelper</groupId>
                 <artifactId>pagehelper</artifactId>
             </dependency>
    
  2. 在SqlSessionFactoryBean配置Mybatis插件

    1. 项目结构

image.png

  1. 在上图的文件中配置代码 ```xml

    mysql true

<a name="GIdet"></a>
#### 2.在AdminMapper.xml中编写SQL语句

1. 项目结构

![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641309360798-98e4d315-988a-4f55-808f-70d84162f7b7.png#clientId=u37fbe2b6-73ce-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=315&id=u8e76ba1f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=630&originWidth=601&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35295&status=done&style=none&taskId=u3fadd056-ca3a-4cfb-9cdb-0567e90f5bc&title=&width=300.5)

2. 在AdminMapper.xml中添加代码
```xml
<!--逆向工程生成的,可以用于引入重复数据-->
<sql id="Base_Column_List" >
  id, login_acct, user_pswd, user_name, email, create_time
</sql>

<!--查找符合关键字匹配的数据(没有关键字则默认查找全部数据)-->
<select id="selectAdminByKeyword" resultMap="BaseResultMap">
  select 
  <!--引入前面的sql标签中的内容-->
  <include refid="Base_Column_List"/>
  from t_admin
  where
  login_acct like CONCAT("%",#{keyword},"%") or
  user_name like CONCAT("%",#{keyword},"%") or
  email like CONCAT("%",#{keyword},"%")
</select>

3.AdminMapper接口中声明方法

  1. 项目结构

image.png

  1. 接口代码

    List<Admin> selectAdminByKeyword(String keyword);
    

    4.AdminService方法

  2. 项目结构

image.png

  1. AdminService方法代码

这里使用PageInfo作为返回值,是因为PageInfo对象中可以携带当前的页码、每页大小、总页数等数据,在前端取值时,比直接返回一个Admin的List更加方便。

/**
     * @param keyword 关键字
     * @param pageNum 当前页码
     * @param pageSize 每一页显示的信息数量
     * @return 最后的pageInfo对象
     */
    public PageInfo<Admin> getPageInfo(String keyword, Integer pageNum, Integer pageSize) {
        // 1.利用PageHelper的静态方法开启分页
        PageHelper.startPage(pageNum,pageSize);

        // 2.调用Mapper接口的对应方法
        List<Admin> admins = adminMapper.selectAdminByKeyword(keyword);

        // 3.为了方便页面的使用,把Admin的List封装成PageInfo(得到页码等数据)
        PageInfo<Admin> pageInfo = new PageInfo<>(admins);

        // 4.返回得到的pageInfo对象
        return pageInfo;
    }

5.AdminHandler方法

  1. 项目结构

image.png

  1. AdminHandler方法代码

    //显示admin的数据
     @RequestMapping("/admin/get/page.html")
     public String getAdminPage(
             // 1.传入的关键字,若未传入,默认值为一个空字符串(不能是null)
             @RequestParam(value = "keyword", defaultValue = "") String keyword,
             // 2.传入的页码,默认值为1
             @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
             // 3.传入的页面大小,默认值为5
             @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
             // 4.ModelMap用于给前端带数据
             ModelMap modelMap) {
    
         // 5.从AdminService中得到对应传参的列表
         PageInfo<Admin> pageInfo = adminService.getPageInfo(keyword, pageNum, pageSize);
    
         // 6.将得到的PageInfo存入modelMap,传给前端
         modelMap.addAttribute(CrowdConstant.NAME_PAGE_INFO,pageInfo);
    
         // 7.进入对应的显示管理员信息的页面(/WEB-INF/admin-page.jsp)
         return "admin-page";
     }
    

    6.前端代码

    在admin-sidebar.jsp页面设置“用户维护”按钮的跳转链接,到controller层的对应RequestMapping

    <li style="height:30px;">
     <a href="admin/page/page.html"><i class="glyphicon glyphicon-user"></i> 用户维护</a>
    </li>
    

    实现在前端页面显示这些得到的数据:
    为了做判断后再获取各个数据,这里需要在admin-page.jsp引入JSTL:

    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    

    admin-page.jsp主要代码: ```html # 账号 名称 邮箱地址 操作

<%— jstl —%> <%— 如果得到的pageInfo的list为空,则表示数据库无数据,显示查不到数据 —%>

抱歉,查不到相关的数据! <%— 数据不为空,通过forEach显示数据(前端显示的主要代码) —%>

${status.count} ${admin.loginAcct} ${admin.userName} ${admin.email}

        <%-- 这里下面的按钮还未用到,先忽略 --%>
        <td>
            <button type="button" class="btn btn-success btn-xs"><i
                    class=" glyphicon glyphicon-check"></i></button>
            <a href="admin/page/update/${admin.id}/${requestScope.pageInfo.pageNum}/${param.keyword}.html" 
               class="btn btn-primary btn-xs"><i class=" glyphicon glyphicon-pencil"></i></a>
            <a href="admin/page/remove/${admin.id}/${requestScope.pageInfo.pageNum}/${param.keyword}.html" 
               class="btn btn-danger btn-xs">
                <i class=" glyphicon glyphicon-remove"></i>
            </a>
        </td>
    </tr>
</c:forEach>

<a name="WbKhz"></a>
#### 7.在页面使用Pagination实现页码导航条

1. 项目结构

![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641313091630-bab5c717-bb67-4d6c-aa5b-e922fa806ab0.png#clientId=u37fbe2b6-73ce-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=505&id=u8f93958e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1009&originWidth=638&originalType=binary&ratio=1&rotation=0&showTitle=false&size=50512&status=done&style=none&taskId=u52574d8c-346f-4226-b8fb-1e6ab8c087b&title=&width=319)

2. 在有需要Pagination的页面引入
```html
<%@include file="/WEB-INF/include-head.jsp" %>
<%--引入pagination的css--%>
<link href="css/pagination.css" rel="stylesheet" />
<%--引入基于jquery的paginationjs--%>
<script type="text/javascript" src="jquery/jquery.pagination.js"></script>
<script type="text/javascript">
  1. 分页导航条需要的代码(JS代码)

回调函数的含义:声明出来以后不是自己调用,而是交给系统或框架调用

<%--引入pagination的css--%>
<link href="css/pagination.css" rel="stylesheet" />
<%--引入基于jquery的paginationjs--%>
<script type="text/javascript" src="jquery/jquery.pagination.js"></script>
<script type="text/javascript">
    $(function () {
        // 调用一个函数
       initPagination();
    });

    function initPagination(){
        // 获取分页数据中的总记录数
        var totalRecord = ${requestScope.pageInfo.total};

        // 声明Pagination设置属性的JSON对象
        var properties = {
            num_edge_entries: 3,                                // 边缘页数
            num_display_entries: 5,                             // 主体页数
            callback: pageSelectCallback,                       // 点击各种翻页反扭时触发的回调函数(执行翻页操作)
            current_page: ${requestScope.pageInfo.pageNum-1},   // 当前页码
            prev_text: "上一页",                                 // 在对应上一页操作的按钮上显示的文本
            next_text: "下一页",                                 // 在对应下一页操作的按钮上显示的文本
            items_per_page: ${requestScope.pageInfo.pageSize}   // 每页显示的数量
        };
        // pagination()方法就是上面的js文件提供的,需要传入总记录数与上面的json对象
        $("#Pagination").pagination(totalRecord,properties);
    }

    // 回调函数的代码
    function pageSelectCallback(pageIndex, jQuery){
        // pageIndex是当前点击的页码的索引,索引从0开始计算,因此比pageNum小1
        var pageNum = pageIndex+1;

        // 执行页面跳转(带页码与关键字)
        window.location.href = "admin/get/page.html?pageNum="+pageNum+"&keyword=${param.keyword}";

        // 取消当前超链接的默认行为
        return false;
    }
</script>
  1. 分页导航条需要的代码(HTML代码)

    <tfoot>
                             <tr>
                                 <td colspan="6" align="center">
                                 <div id="Pagination" class="pagination"><!-- 这里显示分页 --></div>
                                 </td>
                             </tr>
                             </tfoot>
    
  2. 防止jquery.pagination.js源代码出现死循环

这里的jquery.pagination.js源代码会在绘制完整个导航条后,自动调用回调函数,这样会造成代码死循环,因此需要将调用回调函数的代码去掉。
image.png
出现如下死循环:
image.png
解决方法:在jquery.pagination.js源代码中注释掉回调函数

// 回调函数 ( 注:这一句不需要,会造成死循环,因此注释掉)
// opts.callback(current_page, this);

3.关键词查询

1.修改前端页面

1.项目结构

image.png

2.修改代码

找到页面中执行查询操作的部分,修改代码成如下:
主要设置了该form表单的action、method、输入框的name、输入框的value

 <form class="form-inline" action="admin/get/page.html" method="post" role="form" style="float:left;">
                        <div class="form-group has-feedback">
                            <div class="input-group">
                                <div class="input-group-addon">查询条件</div>
                                <input class="form-control has-success" name="keyword" type="text" placeholder="请输入查询条件" value="${param.keyword}"/>
                            </div>
                        </div>
                        <button type="submit" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i> 查询
                        </button>
                    </form>

2.在翻页时保持关键字查询条件

图解:如下
image.png
action指向controller层的查询的方法,且在表单中,附带了 name=”keyword” 的数据,也就将keyword带给了后端,后端通过@RequestParam接收keyword,传递给service层等等后续操作。
且前面的分页js代码中,通过回调函数的跳转链接中给keyword传值:修改admin-page.jsp页面。

window.location.href = "admin/page/page.html?pageNum="+pageNum+"&keyword=${param.keyword}";

可以使在使用换页操作时,仍然带着关键词换页(要注意的是,这里因为有时候keyword并没有,因此keyword传值必须放在链接的最后一个位置,否则可能会引起错误:如==keyword=&pageNum=2==这样的url是有问题的)。

4.单条删除管理员

1.目标

通过点击删除按钮,对管理员数据进行单条删除。

2.思路

image.png

3.代码

1.调整删除按钮的前端代码

  1. 项目结构

image.png

  1. 修改删除按钮的代码如下:

    <a href="admin/page/remove/${admin.id}/${requestScope.pageInfo.pageNum}/${param.keyword}.html" class="btn btn-danger btn-xs">
     <i class=" glyphicon glyphicon-remove"></i>
    </a>
    

    2.AdminHandler方法

    项目结构:
    image.png
    代码:

    //单条删除管理员信息
     @RequestMapping("/admin/page/remove/{adminId}/{pageNum}/{keyword}.html")
     public String removeAdmin(
             // 从前端获取的管理员id
             @PathVariable("adminId") Integer adminId,
             // 从前端获取的当前页码与关键字(为了删除后跳转的页面仍然是刚才的页面,优化体验)
             @PathVariable("pageNum") Integer pageNum,
             @PathVariable("keyword") String keyword){
    
         // 调用service层方法,从数据库根据id删除管理员
         adminService.removeById(adminId);
    
         //删除完成后,重定向(减少数据库操作)返回信息页
         return "redirect:/admin/page/page.html?pageNum="+pageNum+"&keyword="+keyword;
     }
    

    3.AdminService方法

    项目结构
    image.png
    代码:
    注意:(这里其实需要做一些判断:如不能删除现在登录的管理员账号等,但是这里仅仅是作为练习,此处就不写了;另外,在正式的项目中,一般不会将数据库中的信息完全抹去,因为抹去后恢复就很难了,一般可以在表中设置一个状态码,如1表示用户可用,0表示不可用,也就代表被删除了)

    // 根据id删除管理员
     @Override
     public void removeById(Integer adminId) {
         // Mapper接口的方法,根据主键id删除管理员
         adminMapper.deleteByPrimaryKey(adminId);
     }
    

    5.新增管理员

    1.目标

    将表单提交的Admin对象保存到数据库。
    要求1:loginAcct不能重复
    要求2:密码加密

    2.思路

    二.管理员功能 - 图23

    3.代码

    1.在数据库中给t_admin表中的账号字段添加唯一约束

    数据库中给loginAcct设置唯一约束(防止添加管理员时使用重复的登录账号)。
    这里判断login_acct是否唯一最好是放在数据库中判断,如果简单地放后端代码中判断,可能会出现同时添加同一个login_acct操作,由于代码执行前后的原因导致最后写入了一样的login_acct,而通过数据库设置唯一约束,则可以从根本避免出现重复数据。

    ALTER TABLE t_admin ADD UNIQUE INDEX(login_acct)
    

    2.修改前端新增按钮的代码

    <a href="admin/page/save.html" class="btn btn-primary" style="float:right;"><i class="glyphicon glyphicon-plus">新增</i></a>
    

    3.配置view-controller

    在spring-web-mvc.xml文件中配置
    项目结构:
    image.png
    代码:

    <!--前往添加admin页面-->    
    <mvc:view-controller path="/admin/page/save.html" view-name="admin-add"/>
    

    4.准备admin-add.jsp表单页面

    项目结构:
    image.png
    admin-add.jsp页面代码:

    <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
     <ol class="breadcrumb">
         <li><a href="admin/main/page.html">首页</a></li>
         <li><a href="admin/page/page.html">数据列表</a></li>
         <li class="active">新增</li>
     </ol>
     <div class="panel panel-default">
         <div class="panel-heading">表单数据<div style="float:right;cursor:pointer;" data-toggle="modal" data-target="#myModal"><i class="glyphicon glyphicon-question-sign"></i></div></div>
         <div class="panel-body">
             <form action="admin/page/doSave.html" method="post" role="form">
                 <p>${requestScope.exception.message}</p>
                 <div class="form-group">
                     <label for="exampleInputPassword1">登录账号</label>
                     <input type="text" name="loginAcct" class="form-control" id="exampleInputPassword1" placeholder="请输入登录账号">
                 </div>
                 <div class="form-group">
                     <label for="exampleInputPassword1">用户密码</label>
                     <input type="text" name="userPswd" class="form-control" id="exampleInputPassword1" placeholder="请输入用户密码">
                 </div>
                 <div class="form-group">
                     <label for="exampleInputPassword1">用户昵称</label>
                     <input type="text" name="userName" class="form-control" id="exampleInputPassword1" placeholder="请输入用户昵称">
                 </div>
    
                 <div class="form-group">
                     <label for="exampleInputEmail1">邮箱地址</label>
                     <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="请输入邮箱地址">
                     <p class="help-block label label-warning">请输入合法的邮箱地址, 格式为: xxxx@xxxx.com</p>
                 </div>
                 <button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-plus"></i> 新增</button>
                 <button type="reset" class="btn btn-danger"><i class="glyphicon glyphicon-refresh"></i> 重置</button>
             </form>
         </div>
     </div>
    </div>
    

    5.AdminHandler方法

    @RequestMapping("/admin/page/doSave.html")
     public String addAdmin(Admin admin){
         // 1.调用service层存储admin对象的方法
         adminService.saveAdmin(admin);
         // 2.重定向回原本的页面,且为了能在添加管理员后看到管理员,设置pageNum为整型的最大值(通过修正到最后一页)
         return "redirect:/admin/get/page.html?pageNum="+Integer.MAX_VALUE;
     }
    

    6.AdminService方法

    ```java public void saveAdmin(Admin admin) {

     // 1.密码加密
     String pswd = admin.getUserPswd();
     String md5 = CrowdUtil.md5(pswd);
     admin.setUserPswd(md5);
    
     // 2.生成创建时间
     Date date = new Date();
     String format = CrowdUtil.dateFormatUtil(date);
     admin.setCreateTime(format);
    
     // 3.执行保存
     // 执行插入操作
     try {
         adminMapper.insert(admin);
     } catch (Exception e){
         e.printStackTrace();
         // 这里出现异常的话一般就是DuplicateKeyException(因为插入的loginAcct已存在而触发)
         if(e instanceof DuplicateKeyException){
             // 如果确实是DuplicateKeyException,此时抛出一个自定义的异常
             throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
         }
     }
    

    }

<a name="ChHlF"></a>
#### 7.编写一个格式化时间的方法
```java
 /**
     * 对时间进行格式化工具方法
     * @param date
     * @return
     */
    public static String dateFormatUtil(Date date){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

8.处理账号存在的异常方法

  1. AdminService.save()方法中:

    try {
             adminMapper.insert(admin);
         } catch (Exception e){
             e.printStackTrace();
             // 这里出现异常的话一般就是DuplicateKeyException(因为插入的loginAcct已存在而触发)
             if(e instanceof DuplicateKeyException){
                 // 如果确实是DuplicateKeyException,此时抛出一个自定义的异常
                 throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
             }
         }
    

    2.配置异常处理机制
    项目结构:
    image.png
    代码如下:

    //@ControllerAdvice注解标明该类是基于注解的异常处理器类
    @ControllerAdvice
    public class CrowdExceptionResolver {
    
     // 新增管理员时,login_acct已存在,则返回admin-add.jsp页面
     @ExceptionHandler(value = LoginAcctAlreadyInUseException.class)
     public ModelAndView resolverLoginAcctAlreadyInUseException(
             LoginAcctAlreadyInUseException exception, HttpServletRequest request,
             HttpServletResponse response) throws IOException {
         String viewName = "admin-add";
         return commonCode(exception,request,response,viewName);
     }
    }
    

    6.更新管理员

    1.目标

    修改现有Admin的数据,不修改密码,不修改创建时间。

    2.思路

    image.png

    3.代码

    1.修改更新按钮代码

    项目结构
    image.png
    修改代码如下:

    <a href="admin/page/update/${admin.id}/${requestScope.pageInfo.pageNum}/${param.keyword}.html" 
    class="btn btn-primary btn-xs"><i class=" glyphicon glyphicon-pencil"></i></a>
    

    2.回显数据的AdminHandler方法

     @RequestMapping("/admin/page/update/{adminId}/{pageNum}/{keyword}.html")
     public String toUpdatePage(
             @PathVariable("adminId") Integer adminId,
             @PathVariable("pageNum") Integer pageNum,
             @PathVariable("keyword") String keyword,
             ModelMap modelMap){
         // 调用Service方法,通过id查询admin对象
         Admin admin = adminService.queryAdmin(adminId);
    
         // 将admin对象、页码、关键字存入modelMap,传到前端页面
         modelMap.addAttribute("admin", admin);
         modelMap.addAttribute("pageNum", pageNum);
         modelMap.addAttribute("keyword", keyword);
    
         // 跳转到更新页面WEB-INF/admin-update.jsp
         return "admin-update";
     }
    

    3.回显数据的Service方法

    @Override
     public Admin queryAdmin(Integer adminId) {
         return adminMapper.selectByPrimaryKey(adminId);
     }
    

    4.更新页面提交后进入的AdminHandler方法

    @RequestMapping("/admin/page/doUpdate.html")
    public String updateAdmin(Admin admin,@RequestParam("pageNum") Integer pageNum,@RequestParam("keyword") String keyword){
    
     // 调用service层 updateAdmin方法
     adminService.updateAdmin(admin);
    
     // 正常执行则进入原先的管理员信息页面
     return "redirect:/admin/page/page.html?pageNum="+pageNum + "&keyword="+keyword;
    }
    

    5.更新页面的AdminService方法

    @Override
    public void updateAdmin(Admin admin) {
     // 利用try-catch块,处理更新管理员信息时,修改后的loginAcct已经在数据库中存在
     try {
         adminMapper.updateByPrimaryKeySelective(admin);
     } catch (Exception e){
         e.printStackTrace();
         if (e instanceof DuplicateKeyException){
             // 当触发该异常时,抛出另一个针对更新时loginAcct已存在的异常
             throw new LoginAcctAlreadyInUseForUpdateException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
         }
     }
    }
    

    6.编写异常处理类的代码

    项目结构:
    image.png
    编写异常处理机制代码:

    // 更新时,不应将账号改为与其他账号同名
    @ExceptionHandler(value = LoginAcctAlreadyInUseForUpdateException.class)
    public ModelAndView resolverLoginAcctAlreadyInUseForUpdateException(
         LoginAcctAlreadyInUseForUpdateException exception, HttpServletRequest request,
         HttpServletResponse response) throws IOException {
     // 此时进入的是system-error.jsp的页面
     String viewName = "system-error";
     return commonCode(exception,request,response,viewName);
    }