目录
前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。
1). 目前现状
用户如果不登录,直接访问系统首页面,照样可以正常访问。

2). 理想效果
上述这种设计并不合理,我们希望看到的效果应该 是,只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。

那么,具体应该怎么实现呢?
可以使用我们之前讲解过的 过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。
过滤器具体的处理逻辑如下:

A. 获取本次请求的URI
B. 判断本次请求, 是否需要登录, 才可以访问
C. 如果不需要,则直接放行
D. 判断登录状态,如果已登录,则直接放行
E. 如果未登录, 则返回未登录结果
如果未登录,我们需要给前端返回什么样的结果呢? 这个时候, 我们可以去看看前端是如何处理的 ?

1). 定义登录校验过滤器
自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口, 在doFilter方法中完成校验的逻辑。 那么接下来, 我们就根据上述分析的步骤, 来完成具体的功能代码实现:
所属包: com.itheima.reggie.filter
- import com.alibaba.fastjson.JSON;
- import com.itheima.reggie.common.R;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.util.AntPathMatcher;
-
- import javax.servlet.*;
- import javax.servlet.annotation.WebFilter;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- /**
- * 检查用户是否已经完成登录
- */
- @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
- @Slf4j
- public class LoginCheckFilter implements Filter{
- //路径匹配器,支持通配符
- public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
-
- //1、获取本次请求的URI
- String requestURI = request.getRequestURI();// /backend/index.html
-
- log.info("拦截到请求:{}",requestURI);
-
- //定义不需要处理的请求路径
- String[] urls = new String[]{
- "/employee/login",
- "/employee/logout",
- "/backend/**",
- "/front/**"
- };
-
- //2、判断本次请求是否需要处理
- boolean check = check(urls, requestURI);
-
- //3、如果不需要处理,则直接放行
- if(check){
- log.info("本次请求{}不需要处理",requestURI);
- filterChain.doFilter(request,response);
- return;
- }
-
- //4、判断登录状态,如果已登录,则直接放行
- if(request.getSession().getAttribute("employee") != null){
- log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
- filterChain.doFilter(request,response);
- return;
- }
-
- log.info("用户未登录");
- //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
- response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
- return;
-
- }
-
- /**
- * 路径匹配,检查本次请求是否需要放行
- * @param urls
- * @param requestURI
- * @return
- */
- public boolean check(String[] urls,String requestURI){
- for (String url : urls) {
- boolean match = PATH_MATCHER.match(url, requestURI);
- if(match){
- return true;
- }
- }
- return false;
- }
- }
AntPathMatcher 拓展:
介绍: Spring中提供的路径匹配器 ;
通配符规则:
符号 含义 ? 匹配一个字符 * 匹配0个或多个字符 ** 匹配0个或多个目录/字符
2). 开启组件扫描
需要在引导类上, 加上Servlet组件扫描的注解, 来扫描过滤器配置的@WebFilter注解, 扫描上之后, 过滤器在运行时就生效了。
- @Slf4j
- @SpringBootApplication
- @ServletComponentScan
- public class ReggieApplication {
- public static void main(String[] args) {
- SpringApplication.run(ReggieApplication.class,args);
- log.info("项目启动成功...");
- }
- }
@ServletComponentScan 的作用:
在SpringBoot项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的@WebServlet , @WebFilter , @WebListener 注解, 自动注册Servlet的相关组件 ;
代码编写完毕之后,我们需要将工程重启一下,然后在浏览器地址栏直接输入系统管理后台首页,然后看看是否可以跳转到登录页面即可。我们也可以通过debug的形式来跟踪一下代码执行的过程。

对于前端的代码, 也可以进行debug调试。
F12打开浏览器的调试工具, 找到我们前面提到的request.js, 在request.js的响应拦截器位置打上断点。

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面,如下:
当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。
新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。employee表中的status字段已经设置了默认值1,表示状态正常。

需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的。

在开发代码之前,我们需要结合着前端页面发起的请求, 梳理一下整个程序的执行过程:

A. 点击"保存"按钮, 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端, 请求方式POST, 请求路径 /employee
B. 服务端Controller接收页面提交的数据并调用Service将数据进行保存
C. Service调用Mapper操作数据库,保存数据
在EmployeeController中增加save方法, 用于保存用户员工信息。
A. 在新增员工时, 按钮页面原型中的需求描述, 需要给员工设置初始默认密码 123456, 并对密码进行MD5加密。
B. 在组装员工信息时, 还需要封装创建时间、修改时间,创建人、修改人信息(从session中获取当前登录用户)。
- /**
- * 新增员工
- * @param employee
- * @return
- */
- @PostMapping
- public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
- log.info("新增员工,员工信息:{}",employee.toString());
-
- //设置初始密码123456,需要进行md5加密处理
- employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
-
- employee.setCreateTime(LocalDateTime.now());
- employee.setUpdateTime(LocalDateTime.now());
-
- //获得当前登录用户的id
- Long empId = (Long) request.getSession().getAttribute("employee");
-
- employee.setCreateUser(empId);
- employee.setUpdateUser(empId);
-
- employeeService.save(employee);
- return R.success("新增员工成功");
- }
代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮, 输入员工基本信息, 然后点击 "保存" 进行数据保存, 保存完毕后, 检查数据库中是否录入员工数据。
当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现错误提示信息:

而此时,服务端已经报错了, 报错信息如下:

出现上述的错误, 主要就是因为在 employee 表结构中,我们针对于username字段,建立了唯一索引,添加重复的username数据时,违背该约束,就会报错。但是此时前端提示的信息并不具体,用户并不知道是因为什么原因造成的该异常,我们需要给用户提示详细的错误信息 。
2.6.1 思路分析
要想解决上述测试中存在的问题,我们需要对程序中可能出现的异常进行捕获,通常有两种处理方式:
A. 在Controller方法中加入 try...catch 进行异常捕获
形式如下:

如果采用这种方式,虽然可以解决,但是存在弊端,需要我们在保存其他业务数据时,也需要在Controller方法中加上try...catch进行处理,代码冗余,不通用。
B. 使用异常处理器进行全局异常捕获
采用这种方式来实现,我们只需要在项目中定义一个通用的全局异常处理器,就可以解决本项目的所有异常。
2.6.2 全局异常处理器
在项目中自定义一个全局异常处理器,在异常处理器上加上注解 @ControllerAdvice,可以通过属性annotations指定拦截哪一类的Controller方法。 并在异常处理器的方法上加上注解 @ExceptionHandler 来指定拦截的是那一类型的异常。
异常处理方法逻辑:
指定捕获的异常类型为 SQLIntegrityConstraintViolationException
解析异常的提示信息, 获取出是那个值违背了唯一约束
组装错误信息并返回

所属包: com.itheima.reggie.common
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.ControllerAdvice;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.ResponseBody;
- import org.springframework.web.bind.annotation.RestController;
- import java.sql.SQLIntegrityConstraintViolationException;
-
- /**
- * 全局异常处理
- */
- @ControllerAdvice(annotations = {RestController.class, Controller.class})
- @ResponseBody
- @Slf4j
- public class GlobalExceptionHandler {
-
- /**
- * 异常处理方法
- * @return
- */
- @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
- public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
- log.error(ex.getMessage());
- if(ex.getMessage().contains("Duplicate entry")){
- String[] split = ex.getMessage().split(" ");
- String msg = split[2] + "已存在";
- return R.error(msg);
- }
- return R.error("未知错误");
- }
- }
注解说明:
上述的全局异常处理器上使用了的两个注解 @ControllerAdvice , @ResponseBody , 他们的作用分别为:
@ControllerAdvice : 指定拦截那些类型的控制器;
@ResponseBody: 将方法的返回值 R 对象转换为json格式的数据, 响应给页面;
上述使用的两个注解, 也可以合并成为一个注解 @RestControllerAdvice
2.6.3 测试
全局异常处理器编写完毕之后,我们需要将项目重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮。当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现如下错误提示信息:
