【尚筹网】五、管理员维护
- 任务清单
- 分页管理管理员信息
- 目标
- 思路
- 代码
- 引入 PageHelper
- AdminMapper 中编写 SQL 语句
- AdminMapper 接口生成方法
- AdminService
- AdminHandler
- 页面显示主体
- 在页面上使用 `Pagination` 实现导航条
- 关键词查询
- 页面上调整表单
- 在翻页时保持关键词查询条件
- 单条删除
- 目标
- 思路
- 代码
- 调整删除的按钮
- AdminHandler.remove()
- AdminService.remove()
- 新增
- 目标
- 思路
- 在 `t_admin` 表中给账号添加唯一约束
- 调整修改按钮
- 配置 `view-controller`
- 准备表单页面
- handler 方法
- Service 方法
- 处理唯一约束
- 创建自定义异常类
- 在异常处理器类添加方法
- 修改 Service 方法
- 更新
- 目标
- 思路
- 回显表单
- 调整铅笔按钮
- Handler 方法
- 准备页面
- Service 方法
- 执行更新
- Handler 方法
- Service 方法
- 创建自定义异常
- 在异常处理器类添加方法
- 修改 Service 方法
任务清单
- 分页显示 Admin 数据
- 不带关键词分页
- 带关键词分页
- 新增 Admin
- 更新 Admin
- 单条删除 Admin
分页管理管理员信息
目标
将数据库中的 Admin 数据在页面上以分页形式显示。在后端将“带关键词”和“不带关键词”的分页合并为同一套代码
思路
代码
引入 PageHelper
-
确认是否加入了依赖
<!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency>
-
在
SqlSessionFactoryBean
配置 MyBatis 插件
<!-- 配置 SqlSessionFactoryBean 整合 MyBatis -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean" >
<!-- 指定 MyBatis 全局配置文件位置 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
<!-- 指定 Mapper.xml 配置文件位置 -->
<property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml" />
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置插件 -->
<property name="plugins" >
<array>
<!-- 配置 PageHelper -->
<bean class="com.github.pagehelper.PageHelper">
<property name="properties">
<props>
<!-- 配置数据库方言,告诉 PageHelper 当前使用的数据库 -->
<prop key="dialect">mysql</prop>
<!-- 配置页码的合理化修正,在1~总页数之间修正页码 -->
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
AdminMapper 中编写 SQL 语句
<select id="selectAdminByKeyword" resultMap="BaseResultMap">
select id, login_acct, user_pswd, user_name, email, create_time
from t_admin
where login_acct like concat('%',#{keyword},'%')
or user_name like concat('%',#{keyword},'%')
or email like concat('%',#{keyword},'%')
</select>
AdminMapper 接口生成方法
List<Admin> selectAdminByKeyword(String keyword);
AdminService
@Override
public PageInfo<Admin> getPageInfo(String keyword, Integer pageNum, Integer pageSize) {
// 1.调用 PageHelper 的静态方法开启分页功能
// 这里充分体现了 PageHelper 的“非侵入式”设计:原本要做的查询不必有任何修改
PageHelper.startPage(pageNum, pageSize);
// 2.执行查询
List<Admin> list = adminMapper.selectAdminByKeyword(keyword);
// 3.封装为 PageInfo 对象返回
return new PageInfo<>(list);
}
AdminHandler
@RequestMapping("/admin/get/page.html")
public String getPageInfo(
// 使用 @RequestParam 注解的 defaultValue 属性,指定默认值,在请求中没有携带对应参数时使用默认值
// keyword 默认值使用空字符串,和 SQL 语句配合实现两种情况的适配
@RequestParam(value = "keyword", defaultValue = "") String keyword,
// pageNum 默认值使用 1
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
// pageSize 默认值使用 5
@RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
ModelMap modelMap
){
// 调用 Service 方法获取 PageInfo 对象
PageInfo<Admin> pageInfo = adminService.getPageInfo(keyword, pageNum, pageSize);
// 将 PageInfo 对象存入模型
modelMap.addAttribute(CrowdConstant.ATTR_NAME_PAGE_INFO, pageInfo);
return "admin-page";
}
页面显示主体
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<%@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">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="glyphicon glyphicon-th"></i> 数据列表</h3>
</div>
<div class="panel-body">
<form class="form-inline" 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" type="text"
placeholder="请输入查询条件">
</div>
</div>
<button type="button" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i>
查询
</button>
</form>
<button type="button" class="btn btn-danger" style="float:right;margin-left:10px;"><i
class=" glyphicon glyphicon-remove"></i> 删除
</button>
<button type="button" class="btn btn-primary" style="float:right;"
onclick="window.location.href='add.html'"><i class="glyphicon glyphicon-plus"></i>
新增
</button>
<br>
<hr style="clear:both;">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th width="30">#</th>
<th width="30"><input type="checkbox"></th>
<th>账号</th>
<th>名称</th>
<th>邮箱地址</th>
<th width="100">操作</th>
</tr>
</thead>
<tbody>
<c:if test="${empty requestScope.pageInfo.list}">
<tr>
<td colspan="6" align="center">抱歉!没有查询到您要的数据!</td>
</tr>
</c:if>
<c:if test="${!empty requestScope.pageInfo.list}">
<c:forEach items="${requestScope.pageInfo.list}" var="admin" varStatus="myStatus">
<tr>
<td>${myStatus.count}</td>
<td><input type="checkbox"></td>
<td>${admin.loginAcct}</td>
<td>${admin.userName}</td>
<td>${admin.email}</td>
<td>
<button type="button" class="btn btn-success btn-xs">
<i class=" glyphicon glyphicon-check"></i>
</button>
<button type="button" class="btn btn-primary btn-xs">
<i class=" glyphicon glyphicon-pencil"></i>
</button>
<button type="button" class="btn btn-danger btn-xs">
<i class=" glyphicon glyphicon-remove"></i>
</button>
</td>
</tr>
</c:forEach>
</c:if>
</tbody>
<tfoot>
<tr>
<td colspan="6" align="center">
<ul class="pagination">
<li class="disabled"><a href="#">上一页</a></li>
<li class="active"><a href="#">1 <span
class="sr-only">(current)</span></a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><a href="#">下一页</a></li>
</ul>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
在页面上使用 Pagination
实现导航条
在有需要的页面进行引入
<%@include file="/WEB-INF/include-head.jsp" %>
<link rel="stylesheet" href="css/pagination.css" />
<script type="text/javascript" src="jquery/jquery.pagination.js"></script>
-
HTML代码准备
使用
Pagination
要求的 div 标签替换原有的页码部分<tfoot> <tr> <td colspan="6" align="center"> <ul class="pagination"> <li class="disabled"><a href="#">上一页</a></li> <li class="active"><a href="#">1 <span class="sr-only">(current)</span></a></li> <li><a href="#">2</a></li> <li><a href="#">3</a></li> <li><a href="#">4</a></li> <li><a href="#">5</a></li> <li><a href="#">下一页</a></li> </ul> </td> </tr> </tfoot>
<tfoot> <tr> <td colspan="6" align="center"> <div id="Pagination" class="pagination"></div> </td> </tr> </tfoot>
-
JQuery 代码
<script type="text/javascript"> $(function () { // 对页面导航条进行初始化 initPagination(); }); // 初始化分页导航条 function initPagination(){ // 获取总记录数 var totalRecord = ${requestScope.pageInfo.total}; // 声明一个 JSON 对象存储分页导航条需要用到的数据 var properties = { // 边缘页数 num_edge_entries: 3, // 主体页数 num_display_entries: 5, // 指定用户点击翻页的按钮时跳转页面的回调函数 callback: pageSelectCallback, // 每页要显示的记录数 items_per_page: ${requestScope.pageInfo.pageSize}, // 当前页码, Pagination 内部使用 pageIndex 来管理页码,pageIndex 从0开始,pageNum 从1开始,所以 pageNum - 1 current_page: ${requestScope.pageInfo.pageNum - 1}, prev_text: "上一页", next_text: "下一页" } // 生成页码导航条 $('#Pagination').pagination(totalRecord, properties); } // 回调函数的含义:声明出来以后不是自己调用,而是交给系统或框架调用 // 用户点击“上一页、下一页、1、2、3."这样的页码时调用这个函数实现页面跳转 // pageIndex 是 Pagination 传给我们的那个从0开始的页码 function pageSelectCallback(pageIndex, jQuery){ // 根据 pageIndex 计算得到 pageNum var pageNum = pageIndex + 1; // 跳转到指定页 window.location.href = "admin/get/page.html?pageNum=" + pageNum; // 由于每一个页码都是超链接,所以在这个函数最后取消超链接的默认行为 return false; } </script>
-
修改
Pagination
源码// 所有初始化完成,绘制链接 drawLinks(); // 回调函数 // opts.callback(current_page, this);
关键词查询
页面上调整表单
<form class="form-inline" role="form" style="float:left;" action="admin/get/page.html" method="post">
<div class="form-group has-feedback">
<div class="input-group">
<div class="input-group-addon">查询条件</div>
<label>
<input name="keyword" class="form-control has-success" type="text"
placeholder="请输入查询条件">
</label>
</div>
</div>
<button type="submit" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i>
查询
</button>
</form>
在翻页时保持关键词查询条件
window.location.href = "admin/get/page.html?pageNum=" + pageNum + "&keyword=${param.keyword}";
单条删除
目标
在页面上点击单条删除按钮 ,实现 Admin 对应记录的删除
思路
代码
调整删除的按钮
<%-- <button type="button" class="btn btn-danger btn-xs">
<i class=" glyphicon glyphicon-remove"></i>
</button> --%>
<a href="admin/remove/${admin.id}/${requestScope.pageInfo.pageNum}/${param.keyword}.html" class="btn btn-danger btn-xs">
<i class=" glyphicon glyphicon-remove"></i>
</a>
AdminHandler.remove()
@RequestMapping("admin/remove/{adminId}/{pageNum}/{keyword}.html")
public String remove(@PathVariable("adminId") Integer adminId,
@PathVariable("pageNum") Integer pageNum,
@PathVariable("keyword") String keyword){
// 调用 Service 方法执行删除
adminService.remove(adminId);
// 页面跳转:回到分页页面
// 尝试方案1:直接转发到 admin-page.jsp 会无法显示分页数据
// return "admin-page";
// 尝试方案2:转发到 /admin/get/page.html 页面,一旦刷新页面会重复执行删除浪费性能
// return "forward:/admin/get/page.html";
// 尝试方案3:重定向到 /admin/get/page.html 页面
// 同时为了保持原本所在的页面和查询关键词再附加 pageNum 和 keyword 两个请求参数
return "redirect:/admin/get/page.html?pageNum=" + pageNum + "&keyword=" + keyword;
}
AdminService.remove()
@Override
public void remove(Integer adminId) {
adminMapper.deleteByPrimaryKey(adminId);
}
新增
目标
将表单提交的 Admin 对象保存到数据库中
要求:
- loginAcct 不能重复
- 密码加密
思路
在 t_admin
表中给账号添加唯一约束
alter table `project_crowd`.`t_admin` add unique index (`login_acct`);
调整修改按钮
<%-- <button type="button" class="btn btn-primary" style="float:right;"
onclick="window.location.href='add.html'"><i class="glyphicon glyphicon-plus"></i>
新增
</button> --%>
<a href="admin/to/add/page.html" class="btn btn-primary" style="float:right;"
onclick="window.location.href='add.html'"><i class="glyphicon glyphicon-plus"></i>
新增
</a>
配置 view-controller
<mvc:view-controller path="/admin/to/add/page.html" view-name="admin-add" />
准备表单页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<%@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">
<ol class="breadcrumb">
<li><a href="/admin/to/main/page.html">首页</a></li>
<li><a href="/admin/get/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/save.html" method="post" role="form">
<p>${requestScope.exception.message}</p>
<div class="form-group">
<label for="inputLoginAcct">登录账号</label>
<input type="text" class="form-control" id="inputLoginAcct" name="loginAcct"
placeholder="请输入登录账号">
</div>
<div class="form-group">
<label for="inputPassword">登录密码</label>
<input type="text" class="form-control" id="inputPassword" name="userPswd"
placeholder="请输入登录密码">
</div>
<div class="form-group">
<label for="inputUserName">用户昵称</label>
<input type="text" class="form-control" id="inputUserName" name="userName"
placeholder="请输入用户昵称">
</div>
<div class="form-group">
<label for="inputEmail">邮箱地址</label>
<input type="email" class="form-control" id="inputEmail" name="email"
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>
</div>
</div>
</body>
</html>
handler 方法
@RequestMapping("admin/save.html")
public String save(Admin admin){
adminService.saveAdmin(admin);
return "redirect:/admin/get/page.html?pageNum=" + Integer.MAX_VALUE;
}
Service 方法
public void saveAdmin(Admin admin) {
// 1.密码加密
String userPswd = admin.getUserPswd();
userPswd = CrowdUtil.md5(userPswd);
admin.setUserPswd(userPswd);
// 2.生成创建时间
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String createTime = format.format(date);
admin.setCreateTime(createTime);
// 3.执行插入
adminMapper.insert(admin);
}
处理唯一约束
创建自定义异常类
package com.atguigu.crowd.exception;
/**
* Copyright (C) 2024 - 2024 Jasonakeke, Inc. All Rights Reserved
*
* @Desc : 保存或者更新 Admin 时,如果检测到登录账号重复则抛出这个异常
* @Time : 2024/12/1 22:05
* @Author : Code_By_Jasonakeke
* @Email : 2284037977@qq.com
* @Class : LoginAcctAlreadyInUseException
* @IDE : IntelliJ IDEA
*/
public class LoginAcctAlreadyInUseException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LoginAcctAlreadyInUseException(String message) {
super(message);
}
public LoginAcctAlreadyInUseException(String message, Throwable cause) {
super(message, cause);
}
public LoginAcctAlreadyInUseException(Throwable cause) {
super(cause);
}
public LoginAcctAlreadyInUseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public LoginAcctAlreadyInUseException() {
}
}
在异常处理器类添加方法
@ExceptionHandler(LoginAcctAlreadyInUseException.class)
public ModelAndView resolveLoginAcctAlreadyInUseException(LoginAcctAlreadyInUseException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "admin-add";
return commonResolve(viewName, exception, request, response);
}
修改 Service 方法
public void saveAdmin(Admin admin) {
// 1.密码加密
String userPswd = admin.getUserPswd();
userPswd = CrowdUtil.md5(userPswd);
admin.setUserPswd(userPswd);
// 2.生成创建时间
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String createTime = format.format(date);
admin.setCreateTime(createTime);
// 3.执行插入
try {
adminMapper.insert(admin);
} catch (Exception e) {
e.printStackTrace();
logger.info("异常全类名:{}", e.getClass().getName());
if (e instanceof DuplicateKeyException) {
throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
}
}
}
更新
目标
修改现有 Admin 的数据,不修改密码和创建时间
思路
回显表单
调整铅笔按钮
<%-- <button type="button" class="btn btn-primary btn-xs">
<i class=" glyphicon glyphicon-pencil"></i>
</button> --%>
<a href="admin/to/edit/page.html?adminId=${admin.id}&pageNum=${requestScope.pageInfo.pageNum}&keyword=${param.keyword}" class="btn btn-primary btn-xs">
<i class="glyphicon glyphicon-pencil"></i>
</a>
Handler 方法
@RequestMapping("admin/to/edit/page.html")
public String toEditPage(
@RequestParam("adminId") Integer adminId,
ModelMap modelMap
){
// 1.根据 adminId 查询 Admin 对象
Admin admin = adminService.getAdminById(adminId);
// 2.将 Admin 对象存入模型
modelMap.addAttribute("admin", admin);
return "admin-edit";
}
准备页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<%@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">
<ol class="breadcrumb">
<li><a href="/admin/to/main/page.html">首页</a></li>
<li><a href="/admin/get/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/update.html" method="post" role="form">
<input type="hidden" name="id" value="${requestScope.admin.id}">
<input type="hidden" name="pageNum" value="${param.pageNum}">
<input type="hidden" name="keyword" value="${param.keyword}">
<p>${requestScope.exception.message}</p>
<div class="form-group">
<label for="inputLoginAcct">登录账号</label>
<input type="text" class="form-control" id="inputLoginAcct" name="loginAcct" value="${requestScope.admin.loginAcct}"
placeholder="请输入登录账号">
</div>
<div class="form-group">
<label for="inputUserName">用户昵称</label>
<input type="text" class="form-control" id="inputUserName" name="userName" value="${requestScope.admin.userName}"
placeholder="请输入用户昵称">
</div>
<div class="form-group">
<label for="inputEmail">邮箱地址</label>
<input type="email" class="form-control" id="inputEmail" name="email" value="${requestScope.admin.email}"
placeholder="请输入邮箱地址">
<p class="help-block label label-warning">请输入合法的邮箱地址, 格式为:
xxxx@xxxx.com</p>
</div>
<button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-edit"></i>
更新
</button>
<button type="reset" class="btn btn-danger"><i class="glyphicon glyphicon-refresh"></i>
重置
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Service 方法
@Override
public Admin getAdminById(Integer adminId) {
return adminMapper.selectByPrimaryKey(adminId);
}
执行更新
Handler 方法
]
@RequestMapping("admin/update.html")
public String update(Admin admin,
@RequestParam("pageNum") Integer pageNum,
@RequestParam("keyword") String keyword){
adminService.update(admin);
return "redirect:/admin/get/page.html?pageNum=" + pageNum + "&keyword=" + keyword;
}
Service 方法
@Override
public void update(Admin admin) {
try {
// Selective:表示有选择的更新,对于 null 值的字段不更新
adminMapper.updateByPrimaryKeySelective(admin);
} catch (Exception e) {
e.printStackTrace();
logger.info("异常全类名:{}", e.getClass().getName());
if (e instanceof DuplicateKeyException) {
throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
}
}
}
创建自定义异常
package com.atguigu.crowd.exception;
/**
* Copyright (C) 2024 - 2024 Jasonakeke, Inc. All Rights Reserved
*
* @Desc :
* @Time : 2024/12/1 23:12
* @Author : Code_By_Jasonakeke
* @Email : 2284037977@qq.com
* @Class : LoginAcctAlreadyInUseForUpdateException
* @IDE : IntelliJ IDEA
*/
public class LoginAcctAlreadyInUseForUpdateException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LoginAcctAlreadyInUseForUpdateException() {
}
public LoginAcctAlreadyInUseForUpdateException(String message) {
super(message);
}
public LoginAcctAlreadyInUseForUpdateException(String message, Throwable cause) {
super(message, cause);
}
public LoginAcctAlreadyInUseForUpdateException(Throwable cause) {
super(cause);
}
public LoginAcctAlreadyInUseForUpdateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
在异常处理器类添加方法
@ExceptionHandler(LoginAcctAlreadyInUseForUpdateException.class)
public ModelAndView resolveLoginAcctAlreadyInUseForUpdateException(LoginAcctAlreadyInUseForUpdateException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "system-error";
return commonResolve(viewName, exception, request, response);
}
修改 Service 方法
@Override
public void update(Admin admin) {
try {
// Selective:表示有选择的更新,对于 null 值的字段不更新
adminMapper.updateByPrimaryKeySelective(admin);
} catch (Exception e) {
e.printStackTrace();
logger.info("异常全类名:{}", e.getClass().getName());
if (e instanceof DuplicateKeyException) {
throw new LoginAcctAlreadyInUseForUpdateException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
}
}
}