某马瑞吉外卖单体架构项目完整开发文档,基于 Spring Boot 2.7.11 + JDK 11。预计 5 月 20 日前更新完成,有需要的胖友记得一键三连,关注主页 “瑞吉外卖” 专栏获取最新文章。
相关资料:https://pan.baidu.com/s/1rO1Vytcp67mcw-PDe_7uIg?pwd=x548
提取码:x548
文章目录
- 1.问题分析
- 2.代码实现
- 3.功能测试
1.问题分析
前面虽然我们已经实现了后台系统的员工登录功能,但是其实它并不完善。期中存在的一个问题就是即使我们不通过登陆页面也可以直接通过 “http://localhost:8080/backend/index.html”访问后台主页:
显然,这种设计是不合理的,我们希望的效果应该是,只有登陆成功后才可以访问系统中的页面,如果没有登陆则自动跳转到登陆页面。
具体如何实现呢?我们可以通过过滤器或者拦截器,在过滤器或者拦截器中判读用户是否已经完成登陆,如果没有登陆则跳转到登录界面即可。
具体的处理逻辑如下:
- 获取本次请求的 URI;
- 判断本次请求是否需要处理;
- 如果不需要处理,则直接放行;
- 判断登陆状态,如果已经登陆,则直接放行;
- 如果未登录,则重定向到登录页面。
2.代码实现
创建一个 filter
包用于存放我们的自定义过滤器,我们可以创建一个 LoginCheckFilter
类实现 javax.servlet.Filter
接口,重写接口的 doFilter()
方法以实现我们的登陆检查逻辑。
LoginCheckFilter
类完整代码如下:
package cn.javgo.reggie_take_out.filter;
import cn.javgo.reggie_take_out.common.R;
import com.alibaba.fastjson.JSON;
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;
import java.util.Arrays;
/**
* 用户登录检查过滤器
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = {"/*"})
public class LoginCheckFilter implements Filter {
// 需要忽略的 URI 地址
private static final String[] IGNORE_URI = {"/employee/login","/employee/logout","/backend/**","/front/**"};
// AntPathMatcher 是 Spring 提供的一个通配符匹配类,支持 ?、*、** 等通配符
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
/**
* 初始化过滤器
* @param servletRequest 请求对象
* @param servletResponse 响应对象
* @param filterChain 过滤器链对象
* @throws IOException IO异常
* @throws ServletException Servlet异常
*/
@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();
// 2.判断当前的 URI 地址是否需要忽略
boolean check = Arrays.stream(IGNORE_URI).anyMatch(uri -> ANT_PATH_MATCHER.match(uri, requestURI));
// 3.如果需要忽略,则直接放行
if(check){
filterChain.doFilter(request,response);
return;
}
// 4.如果用户已经登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
filterChain.doFilter(request,response);
return;
}
// 5.用户未登录,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
}
上述代码实现逻辑比较简单,我们挑出部分内容进行简单分析。
-
为什么要忽略
{"/employee/login","/employee/logout","/backend/**","/front/**"}
几个请求?前面两个比较容易理解,用户访问登陆页面或退出登陆的 URI 自然没必要进行登陆检查。至于后面的
{"/backend/**","/front/**"}
也被列为可忽略的 URI 这个并不是必须的,因为如果没有经过登陆页面成功登陆,即便访问成功有也无济于事,因为访问到的页面不会有数据内容。 -
AntPathMatcher
有何用?AntPathMatcher
是 Spring Framework 中的一个工具类,用于进行 Ant 风格的路径匹配(一种常用的路径匹配规则,支持通配符)。AntPathMatcher 可以帮助我们快速实现路径匹配的功能,是 Web 应用中常用的工具类之一。支持以下路径匹配规则:
?
:匹配任意单个字符*
:匹配任意数量的字符,包括 0 个字符**
:匹配任意数量的路径,包括 0 个路径
-
为什么最终要向客户端返回一个错误的 JSON 对象?
在上面的源码中我们为什么莫名其妙地向客户端返回一个
R.error("NOTLOGIN")
,这可能让你感到疑惑,其实这是因为前端也有自己的拦截器,会根据拿到的响应对象中的数据进行判断从而跳转指定页面。在 static/backend/index.html 页面中我们能找到对应发送 AJAX 请求的 js 文件,其中就包括了一个 request.js:我们追踪到 request.js 的拦截器相关位置,便能看到如下关键信息:
处理逻辑也比较简单,当服务端返回的对象中包含
code === 0
且data.msg === 'NOTLOGIN'
时,便会清空浏览器的用户信息,然后跳转到登陆页面。这就是为什么我们在实现过滤器的时候最终返回R.error("NOTLOGIN")
的原因,当然这是为了应承前端的设计,不然我们也可以通过自定义拦截器等方式进行重定向。
最后,想要我们的过滤器生效,还需要再启动类上添加 @ServletComponentScan
注解,以启用自动扫描 Servlet、Filter 和 Servlet 监听器组件。
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieTakeOutApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieTakeOutApplication.class, args);
}
@Bean
CommandLineRunner commandLineRunner(){
return args -> log.info("项目启动成功......");
}
}
在传统的 Servlet 应用程序中,我们通常需要在 web.xml 文件中显式地声明 Servlet、Filter 和 Servlet 监听器等组件。但是,在基于 Spring 的应用程序中,我们可以使用 Spring 提供的 @ServletComponentScan
注解来自动注册这些组件。
当我们在 Spring 应用程序上下文中使用 @ServletComponentScan
注解时,Spring 将扫描指定的包及其子包中的所有 Servlet、Filter 和 Servlet 监听器组件,并将它们自动注册到 Servlet 容器中。这样,我们就可以在不编写冗长的 web.xml 文件的情况下轻松地配置 Servlet 应用程序。
需要注意的是,@ServletComponentScan
注解只会扫描使用了 @WebServlet
、@WebFilter
和 @WebListener
注解的组件,并将它们自动注册到 Servlet 容器中。如果我们使用传统的 Servlet API 来定义这些组件,则需要手动注册它们。这也是为什么我们要在 LoginCheckFilter 过滤器上添加 @WebFilter(filterName = "loginCheckFilter",urlPatterns = {"/*"})
注解的原因,其中指定了过滤器的名称为 “loginCheckFilter”,并过滤所有请求。
3.功能测试
启动程序,如果我们不进行登陆,直接访问后台主页 “http://localhost:8080/backend/index.html”,那么就会自动跳转到登陆页面:
当然,我们还可以直接在前端拦截器的对应位置处,打上断点,再直接访问 “http://localhost:8080/backend/index.html”进行断点调试,查看执行细节:
可以看到,客户端正常拿到了预期的数据,然后按照执行逻辑就会跳转到登陆页面了: