书接上文(欢迎读者阅读我之前发布的crm系列博客)我们接着对crm系统登录模块做一些功能改进。
一、增加非法请求拦截功能
(一)思路分析
1、拦截目的:放行资源给合法访问用户,过滤非法访问用户(在请求目标资源之前执行该拦截)
2、 拦截方法:判断访问合法与否?返回布尔类型值
3、 返回值为布尔类型:
* true:表示合法访问,放行资源;
* false:非法访问,资源不放行,且抛出用户未登录异常
(由全局异常控制类做判断后跳转到登录页面,下面标题二有全局异常控制类介绍)
4、如何判断用户合法与否?
(这里的判断指标是用户是否处于登陆状态)
* 4.1、拿到用户访问时的cookie信息
* 4.2、从cookie获取userId
* 4.3、根据userId去数据库查询是否存在该用户
(二)具体实现代码
1、定义非法访问拦截器
package com.msb.crm.interceptor;
import com.msb.crm.dao.UserMapper;
import com.msb.crm.exceptions.NoLoginException;
import com.msb.crm.utils.LoginUserUtil;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 定义非法访问拦截器
* 该方法返回值:布尔类型
* 继承HandlerInterceptorAdapter适配器
*/
public class NoLoginInterceptor extends HandlerInterceptorAdapter {
@Resource
private UserMapper userMapper;
/**
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//从cookie获取userId
Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
//判断userId是否为空且数据库是否有该用户记录
if (null == userId && userMapper.selectByPrimaryKey(userId)== null){
//抛出未登陆异常(该异常交给全局异常处理器捕获并作处理)
throw new NoLoginException();
}
return true;
}
}
2、定义使拦截器生效的配置类
package com.msb.crm.config;
import com.msb.crm.interceptor.NoLoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* 使拦截器生效的配置类
*/
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
//1、将拦截器实例化(即获得一个放拦截器的变量),采取注解方式实例化(将方法返回的对象交给IOC管理)
@Bean
public NoLoginInterceptor noLoginInterceptor(){
return new NoLoginInterceptor();
}
//2、给拦截器的变量里面放一个拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(noLoginInterceptor())
//设置需要拦截的资源
.addPathPatterns("/**") //默认拦截所有资源
//设置不需要拦截的资源(public包下的资源以及首页、登录页资源
.excludePathPatterns("/css/**","/images/**","/js/**","/lib/**","/index","/user/login");
}
}
以上代码存放目录
二、改进异常处理方式
1、将之前Controller层的try(){}catch{}异常处理方式替换
用全局异常控制器捕捉并处理
2、新建类GlobalExceptionResolver类
全局异常控制器由该类充当
类文件存放位置如下(和启动类同级,都在crm包下)
3、具体实现代码:
package com.msb.crm;
import com.alibaba.fastjson.JSON;
import com.msb.crm.base.ResultInfo;
import com.msb.crm.exceptions.NoLoginException;
import com.msb.crm.exceptions.ParamsException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 根据方法返回值类型对异常分类并捕获处理(取代控制器里的异常捕获)
* 视图
* json
* 如何判断方法返回的是视图 还是 json?
* 约定:如果方法级别配置@ResponseBody 方法响应内容为json 反之 方法响应内容为html页面
*/
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
/**
* 捕获并处理未登陆异常
* 先判断是否抛出未登陆异常,若是则重定向到登录页让用户进行登录。
*/
if(ex instanceof NoLoginException){
//重定向到登录页
ModelAndView mv = new ModelAndView("redirect:/index");
return mv;
}
/**
* 设置默认异常处理(返回视图)
*/
ModelAndView modelAndView = new ModelAndView();
//设置异常信息
modelAndView.setViewName("error");
modelAndView.addObject("code",500);
modelAndView.addObject("msg","系统异常,请稍后再试");
//判断HandlerMethod( instanceof 运算符用于测试对象是否是指定类型(类或子类或接口)的实例)
if (handler instanceof HandlerMethod){
//类型强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
//通过反射获得方法上声明的ResponseBody注解对象
ResponseBody responseBody = handlerMethod.getMethod().getDeclaredAnnotation(ResponseBody.class);
//判断responseBody是否为空(若==空:则表示该方法返回的是视图;否则返回的是Json)
if (responseBody == null){
/**
* 方法返回视图
*/
//判断异常类型
if(ex instanceof ParamsException){
ParamsException p = (ParamsException) ex;
//设置异常信息
modelAndView.addObject("p",p.getCode());
modelAndView.addObject("msg",p.getMsg());
}
return modelAndView;
}else{
/**
* 方法返回数据
*/
//设置默认的异常处理
ResultInfo resultInfo = new ResultInfo();
resultInfo.setCode(500);
resultInfo.setMsg("发生异常,请重试!!");
//判断异常类型是否为自定义异常
if(ex instanceof ParamsException){
ParamsException p = (ParamsException) ex;
resultInfo.setCode(p.getCode());
resultInfo.setMsg(p.getMsg());
}
//设置响应类型及编码格式(响应Json格式的数据)
response.setContentType("application/Json;charset=UTF-8");
//得到字符输出流
PrintWriter out = null;
try {
out = response.getWriter();
//将需要返回的异常对象转换为Json格式字符
String json = JSON.toJSONString(resultInfo);
//输出数据
out.write(json);
} catch (IOException e) {
e.printStackTrace();
} finally {
//如果out不为空,则关闭??????????????????????????????????
if (out != null){
out.close();
}
}
return null;
}
}
return modelAndView;
}
}
Controller层的try(){}catch{} 就可以省略掉了,如下我把他们打了注释
package com.msb.crm.controller;
import com.msb.crm.base.BaseController;
import com.msb.crm.base.ResultInfo;
import com.msb.crm.model.UserModel;
import com.msb.crm.service.UserService;
import com.msb.crm.utils.LoginUserUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 接收前台请求的控制器:对接前台index页面里发送的Ajax登录请求
*/
//@Controller标记该类是一个控制器(控制器负责接收请求、处理请求,并返回响应)
@Controller
@RequestMapping("user")
public class UserController extends BaseController {
//本层会调用service层对象里的方法,在该类里注入service层对象
@Resource
private UserService userService;
@Resource
private ResultInfo resultInfo;
//该注解作用:给前端机动部(index.js)提供访问路径并限定请求方式
@PostMapping("login")
//以jason格式返回对象类型数据要加这个注解@ResponseBody(如果返回页面不需要加该注解)
@ResponseBody
//index.js根据访问路径携带表单内容给UserController发来请求;
// UserController形参接收表单数据,用ResultInfo类型变量返回封装好的userModel信息给index.js
public ResultInfo userLogin(String userName, String userPwd) {
UserModel userModel = userService.userLogin(userName, userPwd);
resultInfo.setResult(userModel);
/*try {
//controller层对service层的数据进行接收(不建议直接用User类对象接收,因为其中有很多我们不需要的属性值)
//所以我们新建一个model层,里面定义UserModel类,用于把我们需要返回的数据信息封装到该类里
UserModel userModel = userService.userLogin(userName, userPwd);
//将service层返回的用户信息用userModel接收
//设置登陆成功后resultInfo的response值为userModel
resultInfo.setResult(userModel);
} catch (ParamsException p) {
//没有异常时执行try里的代码块,否则执行catch里的代码段
resultInfo.setCode(p.getCode());
resultInfo.setMsg(p.getMsg());
p.printStackTrace();
} catch (Exception e) {
resultInfo.setCode(500);
resultInfo.setMsg("登陆失败");
}*/
return resultInfo;
}
@PostMapping("updatePassword")
@ResponseBody
public ResultInfo updateUserPassword(HttpServletRequest request,String oldPassword,String newPassword,String repeatPassword){
//从request中获取cookie中的userIdStr然后调用工具得到userId
Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
//调用service层修改密码方法
userService.updatePassword(userId,oldPassword,newPassword,repeatPassword);
/*try{
//从request中获取cookie中的userIdStr然后调用工具得到userId
Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
//调用service层修改密码方法
userService.updatePassword(userId,oldPassword,newPassword,repeatPassword);
}catch(ParamsException p){
resultInfo.setCode(p.getCode());
resultInfo.setMsg(p.getMsg());
p.printStackTrace();
}catch (Exception e){
resultInfo.setCode(500);
resultInfo.setMsg("修改密码失败!");
e.printStackTrace();
}*/
return resultInfo;
}
/**
* 接收修改密码界面的请求并执行资源返回操作
* @return
*/
@RequestMapping("toPasswordPage")
public String toPasswordPage(){
return "user/password";
}
}
三、增加记住密码功能
1、前端静态页(index.ftl)实现
(1)在前端的index.ftl文件中添加这部分元素
(2)具体代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>后台管理-登陆</title>
<#include "common.ftl">
<link rel="stylesheet" href="${ctx}/css/index.css" media="all">
</head>
<body>
<div class="layui-container">
<div class="admin-login-background">
<div class="layui-form login-form">
<form class="layui-form" action="">
<div class="layui-form-item logo-title">
<h1>CRM后端登录</h1>
</div>
<div class="layui-form-item">
<label class="layui-icon layui-icon-username" for="username"></label>
<input type="text" name="username" lay-verify="required|account" placeholder="用户名或者邮箱" autocomplete="off" class="layui-input" >
</div>
<div class="layui-form-item">
<label class="layui-icon layui-icon-password" for="password"></label>
<input type="password" name="password" lay-verify="required|password" placeholder="密码" autocomplete="off" class="layui-input" >
</div>
<div class="layui-form-item">
<input type="checkbox" name="rememberMe" id="rememberMe" value="true" lay-skin="primary" title="记住密码">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit="" lay-filter="login">登 入</button>
</div>
</form>
</div>
</div>
</div>
<script src = "${ctx}/js/index.js" charset="utf-8"></script>
</body>
</html>
2、机动组(index.js)实现
(1)在前端的index.js文件中更改/添加这部分内容
(2)具体代码
layui.use(['form','jquery','jquery_cookie'], function () {
var form = layui.form,
layer = layui.layer,
$ = layui.jquery,
$ = layui.jquery_cookie($);
// layui 用户登录 表单提交
/**
* 表单submit提交
* form.on('submit(login)',function (data) {
* return false; // 阻止表单提交/跳转操作
* }
*/
form.on('submit(login)',function (data) {
// 键值对形式获取表单元素全部字段内容
data = data.field;
console.log(data);
/**
* 用户名 密码 非空校验
*/
if(data.username=="undefined" || data.username=="" || data.username.trim()==""){
layer.msg("用户名不能为空!");
return false;
}
if(data.password=="undefined" || data.password=="" || data.password.trim()==""){
layer.msg("用户密码不能为空!");
return false;
}
/**
* 对接后台收发请求的具体执行者(被展示页面index.ftl调用),发送Ajax请求,传递用户名与密码,请求的资源路径:用户登录页面
* 指定用post请求方式,请求数据json格式:键名与后台需求变量名一致,值名与前端展示页index.ftl里对应type属性的name属性值一致
*/
$.ajax({
type:"post",
url:ctx+"/user/login",
data:{
//格式~~~~后台形参名:data.前台表单name属性值
userName:data.username,
userPwd:data.password
},
dataType:"json",
success:function (data) {
//data回调函数:是接收到UserController里的返回值resultinfo后被执行的函数
console.log(data)
if(data.code==200){
layer.msg("用户登录成功",function () {
var result =data.result;
//记住我功能实现方法一:
//判断用户是否选择记住密码
// if($("#rememberMe").prop("checked")){
// //用户选则记住,设置cookie对象xx天内生效,用户也设置到cookie中
// $.cookie("userIdStr",result.userIdStr,{expires:7});
// $.cookie("userName",result.userName,{expires:7});
// $.cookie("trueName",result.trueName,{expires:7});
// }else {
// //用户没选择记住密码则cookie仅仅设置用户信息
// $.cookie("userIdStr",result.userIdStr);
// $.cookie("userName",result.userName);
// $.cookie("trueName",result.trueName);
// }
//记住我功能实现方法二:
$.cookie("userIdStr",result.userIdStr);
$.cookie("userName",result.userName);
$.cookie("trueName",result.trueName);
//给type=checkbox的复选框绑定事件:
// 如果用户勾选,则给该用户的cookie里设置expires项:后面的数字为免登陆天数
if($("input[type='checkbox']").is(":checked")){
$.cookie("userIdStr",result.userIdStr,{expires:7});
$.cookie("userName",result.userName,{expires:7});
$.cookie("trueName",result.trueName,{expires:7});
}
window.location.href=ctx+"/main"; //这里的main是访问后台控制器路径
})
}else{
layer.msg(result_data.msg);
}
}
});
//阻止表单提交/跳转操作
return false;
})
});
到此我们crm系统登录模块的基本功能就基本完成了。感谢大家学习观看并提出见解。