1.管理员登录
1.目标
2.思路
3.需要做的事情
需要做的:
- 对存入数据库的密码进行MD5加密
- 在登录界面登录失败时的处理
- 抽取后台页面的公共部分
- 检查登录状态,防止未登录时访问受保护资源的情况
4.代码
1.创建工具方法进行MD5加密
1.项目结构
2.MD5加密的工具方法
在上面的类中加入一个方法public class CrowdUtil {
/**
* 此方法是用于给字符串进行md5加密的工具方法
* @return 进行md5加密后的结果
* @param source 传入要加密的内容
*/
public static String md5(String source){
// 1.判断source是否有效
if (source == null || source.length() == 0) {
//如果传入的加密内容为空或是空字符串,抛出LoginFailedException
// 2.如果不是有效的字符串抛出异常
throw new LoginFailedException(CrowdConstant.MESSAGE_STRING_INVALIDATE);
}
try {
//表示算法名
String algorithm = "md5";
// 3.得到MessageDigest对象,设置加密方式为md5
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 4.将获得的明文字符串转换为字节数组
byte[] input = source.getBytes();
// 5.对转换得到的字节数组进行md5加密
byte[] output = messageDigest.digest(input);
//设置BigInteger的signum
//signum : -1表示负数、0表示零、1表示正数
int signum = 1;
// 6.将字节数组转换成Big Integer
BigInteger bigInteger = new BigInteger(signum,output);
// 7.设置将bigInteger的值按照16进制转换成字符串,最后全部转换成大写,得到最后的加密结果
int radix = 16;
String encoded = bigInteger.toString(radix).toUpperCase();
// 8.返回加密后的字符串
return encoded;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 9.触发异常则返回null
return null;
}
}
2.自定义异常
1.项目结构
2.登录失败后抛出的自定义异常(代码)
```java package com.zh.crowd.exception;
/**
登录失败后抛出的异常 */ public class LoginFailedException extends RuntimeException {
public LoginFailedException() {
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);
}
- 在登录页面显示异常消息(项目结构)
- 在登录页面显示异常消息(代码)
<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.项目结构
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.思路
3.代码
1.创建自定义异常
①首先创建一个自定义异常AccessForbiddenException,在用户未登录时访问受保护资源时抛出:
1.项目结构
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.项目结构
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.任务清单
确认在上图pom.xml中有如下依赖
<!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency>
在SqlSessionFactoryBean配置Mybatis插件
- 项目结构
在上图的文件中配置代码 ```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接口中声明方法
- 项目结构
- 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方法
- 项目结构
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为空,则表示数据库无数据,显示查不到数据 —%>
<%-- 这里下面的按钮还未用到,先忽略 --%>
<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">
- 分页导航条需要的代码(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>
分页导航条需要的代码(HTML代码)
<tfoot> <tr> <td colspan="6" align="center"> <div id="Pagination" class="pagination"><!-- 这里显示分页 --></div> </td> </tr> </tfoot>
防止jquery.pagination.js源代码出现死循环
这里的jquery.pagination.js源代码会在绘制完整个导航条后,自动调用回调函数,这样会造成代码死循环,因此需要将调用回调函数的代码去掉。
出现如下死循环:
解决方法:在jquery.pagination.js源代码中注释掉回调函数
// 回调函数 ( 注:这一句不需要,会造成死循环,因此注释掉)
// opts.callback(current_page, this);
3.关键词查询
1.修改前端页面
1.项目结构
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.在翻页时保持关键字查询条件
图解:如下
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.思路
3.代码
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方法
项目结构:
代码://单条删除管理员信息 @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方法
项目结构
代码:
注意:(这里其实需要做一些判断:如不能删除现在登录的管理员账号等,但是这里仅仅是作为练习,此处就不写了;另外,在正式的项目中,一般不会将数据库中的信息完全抹去,因为抹去后恢复就很难了,一般可以在表中设置一个状态码,如1表示用户可用,0表示不可用,也就代表被删除了)// 根据id删除管理员 @Override public void removeById(Integer adminId) { // Mapper接口的方法,根据主键id删除管理员 adminMapper.deleteByPrimaryKey(adminId); }
5.新增管理员
1.目标
将表单提交的Admin对象保存到数据库。
要求1:loginAcct不能重复
要求2:密码加密2.思路
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文件中配置
项目结构:
代码:<!--前往添加admin页面--> <mvc:view-controller path="/admin/page/save.html" view-name="admin-add"/>
4.准备admin-add.jsp表单页面
项目结构:
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.处理账号存在的异常方法
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.配置异常处理机制
项目结构:
代码如下://@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.目标
2.思路
3.代码
1.修改更新按钮代码
项目结构
修改代码如下:<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.编写异常处理类的代码
项目结构:
编写异常处理机制代码:// 更新时,不应将账号改为与其他账号同名 @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); }