1、管理员登录
1.1、创建Md5加密工具类:
public static String md5(String source) {
//判断source是否生效
if (source == null || source.length() == 0) {
//不是有效的数据
throw new RuntimeException(CrowdConstant.MESSAGE_STRING_INVALIDATE);
}
String algorithm = "md5";
//获取MessageDigest对象
try {
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 获取明文字符串对应的字节数组
byte[] input = source.getBytes();
// 执行加密
byte[] output = messageDigest.digest(input);
// 创建BigInterger对象
int signum = 1;
BigInteger bigInteger = new BigInteger(signum, output);
// 按照十六进制将bigInteger的值转换成字符串
int base = 16;
String encoded = bigInteger.toString(base).toUpperCase();
return encoded;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
1.2、创建登录失败异常
package com.songzhishu.crowd.exception;
/**
* @BelongsProject: CrowdFunding-parent
* @BelongsPackage: com.songzhishu.crowd.exception
* @Author: 斗痘侠
* @CreateTime: 2023-10-29 15:32
* @Description: 登录失败异常
* @Version: 1.0
*/
public class LoginFailedException extends RuntimeException {
private static final long serialVersionUID = 1577454949343343608L;
public LoginFailedException() {
}
public LoginFailedException(String message) {
super(message);
}
public LoginFailedException(String message, Throwable cause) {
super(message, cause);
}
public LoginFailedException(Throwable cause) {
super(cause);
}
public LoginFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
1.3、在异常处理器类中增加登录失败异常的处理
//登录异常
@ExceptionHandler(value = LoginFailedException.class)
public ModelAndView resolveNullPointerException(LoginFailedException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "admin-longin";
return commonResolve(viewName, exception, request, response);
}
1.4、 在登录页面显示异常消息
<p>${requestScope.exception.message}</p>
1.5、Controller方法
@Controller
public class AdminController {
@Autowired
private AdminService adminService;
@RequestMapping(value = "/admin/do/login.html")
public String doLogin(@RequestParam("loginAcct") String loginAcct,
@RequestParam("userPswd") String userPswd,
HttpSession session
) {
// 调用登录检查的方法 返回admin
Admin admin = adminService.getAdminByLoginAcct(loginAcct, userPswd);
// 将登录成功的数据存入session域
session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN,admin);
//跳转后台主页面
return "admin-main";
}
}
1.6 service核心业务
@Override
public Admin getAdminByLoginAcct(String loginAcct, String userPswd) {
// 1查询用户
// 1.1创建adminExample对象
AdminExample adminExample = new AdminExample();
// 1.2创建criteria对象
AdminExample.Criteria criteria = adminExample.createCriteria();
// 1.3 在criteria中添加条件
criteria.andLoginAcctEqualTo(loginAcct);
List<Admin> adminList = adminMapper.selectByExample(adminExample);
// 判断用户
if (adminList == null||adminList.size()==0) {
throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
}
if (adminList.size()>1) {
//数据错误
throw new LoginFailedException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
}
Admin admin = adminList.get(0);
if (admin == null) {
throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
}
// 获取密码
String userPswdDBMD5 = admin.getUserPswd();
// 加密
String userPswdFormMD5 = CrowdUtil.md5(userPswd);
// 比较
if (!(Objects.equals(userPswdDBMD5,userPswdFormMD5))){
throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
}
//返回数据
return admin;
}
1.7、跳转到后台管理页面:
修改控制层代码:为了避免跳转到后台主页面再刷新浏览器导致重复提交登录表单,重定向到目标页面。
//跳转后台主页面
return "redirect:/admin/to/main/page.html";
使用视图控制器是因为,这个页面的访问不需要数据的,直接进行跳转就可以!
<mvc:view-controller path="/admin/to/main/page.html" view-name="admin-main"/>
这里遇见一个小问题就是,跳转后的页面的样式没有生效,然后我以为是可能和浏览器的缓存什么的也有关系,所以就清除数据,然后发现没有效果,就是不理解问什么找不到资源,然后网上查资料说是在配置SpringMVC中的前端控制器将所有的静态资源都给屏蔽啦,然后就导致数据不能正常的访问,然后我记得我也设置啦注解驱动,教程讲要加上一个
<mvc:default-servlet-handler></mvc:default-servlet-handler>
但是加上后不起作用,然后我脑袋突然开窍,我定义啦一个base标签,我访问css资源的标签写在这个base标签上,然后导致找不到数据,哈哈哈哈,以后找不到数据的话可以试试绝对路径!
<c:set var="baseurl" value="${pageContext.request.contextPath }"></c:set>
<script type="text/javascript" src="${baseurl }/scripts/jquery-1.9.1.min.js"></script>
2、登录检查:
将部分资源保护起来,让没有登录的请求不能访问。
2.1、流程:
2.2、实现
2.2.1、创建拦截器类:
/**
* @BelongsProject: CrowdFunding-parent
* @BelongsPackage: com.songzhishu.crowd.mvc.interceptor
* @Author: 斗痘侠
* @CreateTime: 2023-10-30 11:32
* @Description: 登录拦截器
* @Version: 1.0
*/
public class LoginInterceptor extends HandlerInterceptorAdapter {
/**
* @description: 控制器之前执行
* @author: 斗痘侠
* @date: 2023/10/30 11:35
* @param: null
* @return: null
**/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 检测是否登录 获取session中的数据
HttpSession session = request.getSession();
Admin admin = (Admin) session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN);
// 判断
if (admin == null) {
throw new AccessForbiddenException(CrowdConstant.MESSAGE_LOGIN_FORBIDEN);
}
// 不为空 放行
return true;
}
}
2.2.2、自定义异常:
package com.songzhishu.crowd.exception;
/**
* @BelongsProject: CrowdFunding-parent
* @BelongsPackage: com.songzhishu.crowd.exception
* @Author: 斗痘侠
* @CreateTime: 2023-10-30 11:42
* @Description: 表示用户没有登录就访问受保护的资源时的异常
* @Version: 1.0
*/
public class AccessForbiddenException extends RuntimeException{
private static final long serialVersionUID = -1279033257779871422L;
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.2.3、注册拦截器类
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--要拦截的资源 /* 只对应一层路径 /**拦截多层路径-->
<mvc:mapping path="/**"/>
<!--不拦截的资源 登录注册...-->
<mvc:exclude-mapping path="/admin/to/login/page.html"/>
<mvc:exclude-mapping path="/admin/do/login.html"/>
<mvc:exclude-mapping path="/admin/do/logout.html"/>
<!--配置拦截器的类-->
<bean class="com.songzhishu.crowd.mvc.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
3、权限管理之用户
3.1、分页查询(条件和不加条件
3.1.1、配置分页插件:
导入依赖
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.3</version>
</dependency>
配置
<!--配置分页插件-->
<property name="plugins">
<array>
<!-- 传入插件的对象 -->
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
气死啦这个配置,<prop key="helperDialect">mysql</prop>, 耽误我好多时间,我一开始写啦dialect然后一直报错!我就查资料,然后有人用的数组有的人用的list集合,有的人写在MyBatis中,有的人整合到Spring中,有的人全类名写的是com.github.pagehelper.PageInterceptor,也有的人写的是com.github.pagehelper.PageHelper,看啦一整个头大,最后反正我就是不断的试错然后写出来的,反正以后再报错我就知道啦
mapper:
<select id="selectAdminByKeyword" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"></include>
from t_admin
where login_acct like concat("%",#{keyword},"%") or user_name like concat("%",#{keyword},"%") or email like concat("%",#{keyword},"%")
</select>
service
@Override
public PageInfo<Admin> getPageInfo(String keyword, Integer pageNum, Integer pageSize) {
// 开启分页插件
PageHelper.startPage(pageNum,pageSize);
// 调用mapper
List<Admin> adminList= adminMapper.selectAdminByKeyword(keyword);
// 将数据封装到PageInfo
return new PageInfo<>(adminList);
}
controller
@RequestMapping(value = "/admin/get/page.html")
public String getPageInfo(@RequestParam(value = "keyword", defaultValue = "") String keyword,
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
ModelMap modelMap
) {
// 获取pageInfo
PageInfo<Admin> pageInfo = adminService.getPageInfo(keyword, pageNum, pageSize);
// 存入模型
modelMap.addAttribute(CrowdConstant.ATTR_NAME_PAGE_INFO, pageInfo);
return "admin-page";
}
jsp
<%--没有数据--%>
<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>
写这个的时候要使用jstl标签,所以使用之前要先导入jstl的jar包,问题来了他有三个,导入哪一个呐,啧啧啧多试试就知道啦!
3.1.2、分页导航条:
导入js、css后处理前端页面;
<link rel="stylesheet" href="css/pagination.css">
<script type="text/javascript" src="jquery/jquery.pagination.js"></script>
<script type="text/javascript">
$(function () {
// 调用专门的函数初始化分页导航条
initPagination();
});
// 声明一个函数用于初始化 Pagination
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}, // 当前页,pageNum 从 1 开始,必须-1 后才可以赋值
prev_text: "上一页",
next_text: "下一页",
items_per_page:${requestScope.pageInfo.pageSize} // 每页显示 1 项
};
// 调用分页导航条对应的 jQuery 对象的 pagination()方法生成导航条
$("#Pagination").pagination(totalRecord, properties);
}
// 翻页过程中执行的回调函数
// 点击“上一页”、“下一页”或“数字页码”都会触发翻页动作,从而导致当前函数被调用
// pageIndex 是用户在页面上点击的页码数值
function pageSelectCallback(pageIndex, jQuery) {
// pageIndex 是当前页页码的索引,相对于 pageNum 来说,pageIndex 比 pageNum 小 1
var pageNum = pageIndex + 1;
// 执行页面跳转也就是实现“翻页”
window.location.href = "admin/get/page.html?pageNum=" + pageNum;
// 取消当前超链接的默认行为
return false;
}
</script>
显示
<tfoot>
<tr>
<td colspan="6" align="center">
<div id="Pagination" class="pagination"><!-- 这里显示分页 --></div>
</td>
</tr>
</tfoot>
3.2、关键词查询
jsp
<%--条件查询--%>
<form action="admin/get/page.html" method="post" 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 name="keyword" class="form-control has-success" type="text" placeholder="请输入查询条件">
</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}";
3.3、单条删除
jsp
<%--单条删除--%>
<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>
controller
/**
* @description: 单条删除
* @author: 斗痘侠
* @date: 2023/10/31 12:10
* @param: adminId
**/
@RequestMapping(value = "admin/remove/{adminId}/{pageNum}/{keyword}.html")
public String remove(@PathVariable("adminId") Integer adminId,
@PathVariable("pageNum") Integer pageNum,
@PathVariable("keyword") String keyword){
adminService.remove(adminId);
// 返回到管理页面
/*
* 直接转发 admin-page 问题:无法显示分页数据
* 转发:forward:/admin/get/page.html 问题只要一刷新就会执行刚刚的删除操作
* 重定向: 要考虑携带的请求参数 */
return "redirect:/admin/get/page.html?pageNum="+pageNum+"&keyword="+keyword;
}
service
/**
* @description: 单条删除
* @author: 斗痘侠
* @date: 2023/10/31 12:10
* @param: adminId
**/
@Override
public void remove(Integer adminId) {
adminMapper.deleteByPrimaryKey(adminId);
}
这里就是有一点要注意,就是是删除数后的操作,肯定是要回到管理的主页,也就是分页展示,那么返回的方式不同出现的问题不同直直接转发无法展示分页数据,转发后只要一刷新就会执行刚刚的删除的操作,我这里没有异常处理,如果有异常处理的话会跑异常信息体验感很差。重新向的话相对比较合适,但是重定向相当于一次新的请求,要考虑携带的参数的问题,关键字,当前的页面!
3.4、添加数据
controller
/**
* @description: 插入admin数据
* @author: 斗痘侠
* @date: 2023/10/31 17:46
* @param: admin
* @return: java.lang.String
**/
@RequestMapping(value = "/admin/save.html")
public String save(Admin admin) {
adminService.saveAdmin(admin);
return "redirect:/admin/get/page.html?pageNum=" + Integer.MAX_VALUE;
}
service
/**
* @description: 插入单个Admin
* @author: 斗痘侠
* @date: 2023/10/28 20:53
* @param: admin
**/
@Override
public void saveAdmin(Admin admin) {
//密码加密
String userPswd = admin.getUserPswd();
String userPswdMD5 = CrowdUtil.md5(userPswd);
admin.setUserPswd(userPswdMD5);
//生成创建时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String createTime = dateFormat.format(date);
admin.setCreateTime(createTime);
try {
adminMapper.insert(admin);
} catch (Exception e) {
// 检测当前捕获的异常对象,如果是 DuplicateKeyException 类型说明是账号重复导致的
if(e instanceof DuplicateKeyException) {
// 抛出自定义的 LoginAcctAlreadyInUseException 异常
throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
}
// 为了不掩盖问题,如果当前捕获到的不是 DuplicateKeyException 类型的异常,则把当前捕获到的异常对象继续向上抛出
throw e;
}
}
异常类
package com.songzhishu.crowd.exception;
/**
* @BelongsProject: CrowdFunding-parent
* @BelongsPackage: com.songzhishu.crowd.exception
* @Author: 斗痘侠
* @CreateTime: 2023-10-31 16:28
* @Description: 保存数据时账号不唯一时异常
* @Version: 1.0
*/
public class LoginAcctAlreadyInUseException extends RuntimeException{
private static final long serialVersionUID = -3480896207744243870L;
public LoginAcctAlreadyInUseException() {
super();
}
public LoginAcctAlreadyInUseException(String message) {
super(message);
}
public LoginAcctAlreadyInUseException(String message, Throwable cause) {
super(message, cause);
}
public LoginAcctAlreadyInUseException(Throwable cause) {
super(cause);
}
protected LoginAcctAlreadyInUseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
异常映射处理器
//保存异常
@ExceptionHandler(value = LoginAcctAlreadyInUseException.class)
public ModelAndView resolveLoginAcctAlreadyInUseException(LoginAcctAlreadyInUseException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "admin-add";
return commonResolve(viewName, exception, request, response);
}
3.5、添加数据
3.5.1 表单回显:
前端
<%--修改--%>
<a href="admin/to/edit/page.html?adminId=${admin.id}&pageNum=${requestScope.pageInfo.pageNum}&keyword${param.keyword}"
class="btn btn-primary btn-xs">
<iclass=" glyphicon glyphicon-pencil"></i>
</a>
controller
/**
* @description: 根据id查询出来要编辑的admin信息
* @author: 斗痘侠
* @date: 2023/10/31 19:58
* @param: null
* @return: null
**/
@RequestMapping(value = "/admin/to/edit/page.html")
public String toEditPage(
@RequestParam("adminId") Integer adminId,
ModelMap modelMap
) {
Admin admin = adminService.getAdminById(adminId);
modelMap.addAttribute("admin", admin);
return "admin-edit";
}
service
/**
* @description: 根据id查询admin
* @author: 斗痘侠
* @date: 2023/10/31 20:05
* @param: adminId
* @return: com.songzhishu.crowd.entity.Admin
**/
@Override
public Admin getAdminById(Integer adminId) {
return adminMapper.selectByPrimaryKey(adminId);
}
3.5.1 执行修改:
前端
<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="exampleInputPassword1">登录账号</label>
<input
name="loginAcct"
value="${requestScope.admin.loginAcct }"
type="text" class="form-control"
id="exampleInputPassword1" placeholder="请输入登录账号">
</div>
<div class="form-group">
<label for="exampleInputPassword1">用户昵称</label>
<input
name="userName"
value="${requestScope.admin.userName }"
type="text" class="form-control"
id="exampleInputPassword1" placeholder="请输入用户名称">
</div>
<div class="form-group">
<label for="exampleInputEmail1">邮箱地址</label>
<input type="email"
name="email"
value="${requestScope.admin.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-edit"></i> 更新
</button>
<button type="reset" class="btn btn-danger">
<i class="glyphicon glyphicon-refresh"></i> 重置
</button>
</form>
</div>
</div>
</div>
controller
/**
* @description: 修改数据
* @author: 斗痘侠
* @date: 2023/10/31 21:22
* @param: admin
* @param: pageNum
* @param: keyword
* @return: java.lang.String
**/
@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
/**
* @description: 执行修改操作
* @author: 斗痘侠
* @date: 2023/10/31 20:06
* @param: admin
**/
@Override
public void update(Admin admin) {
//修改时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String createTime = dateFormat.format(date);
admin.setCreateTime(createTime);
//有选择的更新
try {
adminMapper.updateByPrimaryKeySelective(admin);
} catch (Exception e) {
if (e instanceof DuplicateKeyException){
throw new LoginAcctAlreadyInUseForUpdateException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
}
throw e;
}
}
异常类:
package com.songzhishu.crowd.exception;
/**
* @BelongsProject: CrowdFunding-parent
* @BelongsPackage: com.songzhishu.crowd.exception
* @Author: 斗痘侠
* @CreateTime: 2023-10-31 21:06
* @Description: TODO
* @Version: 1.0
*/
public class LoginAcctAlreadyInUseForUpdateException extends RuntimeException{
private static final long serialVersionUID = 5642092340219077462L;
public LoginAcctAlreadyInUseForUpdateException() {
super();
}
public LoginAcctAlreadyInUseForUpdateException(String message) {
super(message); }
public LoginAcctAlreadyInUseForUpdateException(String message, Throwable cause) {
super(message, cause); }
public LoginAcctAlreadyInUseForUpdateException(Throwable cause) {
super(cause); }
protected LoginAcctAlreadyInUseForUpdateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace); }
}
异常映射
```java
//更新异常
@ExceptionHandler(value = LoginAcctAlreadyInUseForUpdateException.class)
public ModelAndView resolveLoginAcctAlreadyInUseForUpdateException(LoginAcctAlreadyInUseForUpdateException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "system-error";
return commonResolve(viewName, exception, request, response);
}
```