用户登录成功后,需要把用户的头像在这个位置显示
此外,在这里 要显示用户的名字
这些内容统称为 登录信息
要实现此功能,需要利用spring的拦截器
拦截器可以拦截浏览器的请求,在请求的开始和结束 插入一些代码,从而能够批量解决多个请求共有的业务。
本文主要内容:
在controller下面新建一个包interceptor(拦截器),新建一个类——拦截器AlphaInterceptor,其是接口HandlerInterceptor的实现类
package com.nowcoder.community.controller.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class AlphaInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);//实例化 日志组件, 日志起到调试作用。
// 在Controller之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("preHandle: " + handler.toString());//日志设置为debug级别。handler.toString()就是可视化handler
return true;
}
// 在Controller之后,模板引擎之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.debug("postHandle: " + handler.toString());
}
// 在TemplateEngine模板引擎之后执行,最后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//Exception ex是异常对象,返回异常信息的时候会用到
logger.debug("afterCompletion: " + handler.toString());
}
}
然后在config下 新建一个配置类WebMvcConfig
重新启动项目,打开浏览器,访问控制台看看有没有拦截器
可以看到按顺序执行了 之前定义的三个方法。
另外,因为object是拦截的目标,也打印了
可以通过设置断点,判断 preHandle是否在Controller之前执行
postHandle是否在Controller之后执行。
通过以上 小例子,我们了解了 拦截器是怎么用的:
写一个类,简单配置后可以实现这样很强大的功能。
每次请求都要按以下流程:
下面通过代码实现 此过程
在interceptor新建一个拦截器 LoginTicketInterceptor
package com.nowcoder.community.controller.interceptor;
import com.nowcoder.community.entity.LoginTicket;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.UserService;
import com.nowcoder.community.util.CookieUtil;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Autowired
private HostHolder hostHolder;
@Override//重写preHandle方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从cookie中获取凭证
String ticket = CookieUtil.getValue(request, "ticket");
if (ticket != null) {
// 查询凭证
LoginTicket loginTicket = userService.findLoginTicket(ticket);
// 检查凭证是否有效
if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {//loginTicket.getExpired().after(new Date())表示超时时间晚于当前时间
// 根据凭证查询用户,查到用户后,搬到模板上。
User user = userService.findUserById(loginTicket.getUserId());
// 在本次请求中持有用户,将用户 先暂存。存用户 要考虑到多线程的情况。浏览器访问服务器是多对一的,即一个服务器能处理多对请求,是并发的方式,每个浏览器访问服务器。服务器都会创建一个独立的线程来解决请求,所以服务器处理请求是在 一个多线程的环境。要考虑线程的隔离。需要使用ThreadLocal。
hostHolder.setUser(user);//因此在hostHolder里面初始化并使用ThreadLocal
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
User user = hostHolder.getUser();//得到当前线程持有的User,
if (user != null && modelAndView != null) {//判断二者不为空
modelAndView.addObject("loginUser", user);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();//整个请求执行完后
}
}
在util包下,新建一个类CookieUtil
package com.nowcoder.community.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
public class CookieUtil {
public static String getValue(HttpServletRequest request, String name) {//从cookie中取出值String
if (request == null || name == null) {
throw new IllegalArgumentException("参数为空!");
}
Cookie[] cookies = request.getCookies();//从request中取出cookies,能得到所有cookies对象,得到的是一个数组。
if (cookies != null) {
for (Cookie cookie : cookies) {//遍历cookies
if (cookie.getName().equals(name)) {//判断cookie的name 是否等于传入的参数。
return cookie.getValue();//如果是的话 返回要找的数据。
}
}
}
return null;
}
}
在userService类中添加findLoginTicket方法:
public LoginTicket findLoginTicket(String ticket) {
return loginTicketMapper.selectByTicket(ticket);
}
在在util包下,再次新建一个类HostHolder
在HostHolder里面初始化并使用了ThreadLocal,
package com.nowcoder.community.util;
import com.nowcoder.community.entity.User;
import org.springframework.stereotype.Component;
/**
* 持有用户信息,用于代替session对象.
*/
@Component//放进容器的注解
public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<>();//初始化ThreadLocal
public void setUser(User user) {//存值
users.set(user);
}
public User getUser() {
return users.get();
}//取值
public void clear() {//请求结束,把ThreadLocal里面的User 清除掉,不然只存进去,不清理,会占用太多内存
users.remove();
}
}
关于ThreadLocal讲解如下:
https://blog.csdn.net/Mr_zhang66/article/details/116356313
写好LoginTicketInterceptor后,需要在WebMvcConfig进行配置
@Autowired
private LoginTicketInterceptor loginTicketInterceptor;
registry.addInterceptor(loginTicketInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
之后改写templates下 index 中 header部分
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" th:href="@{/index}">首页</a> <!-- 首页的访问路径 @{/index} -->
</li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}"><!--没有登录,则看不到任何消息;登录了,即loginUser!=null才会显示 -->
<a class="nav-link position-relative" href="site/letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}"><!-- 和 消息正好相反 ;-没有登录即 loginUser==null 才会显示-->
<a class="nav-link" th:href="@{/register}">注册</a> <!-- 注册的访问路径 "@{/register}"-->
</li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}">
<a class="nav-link" th:href="@{/login}">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown" th:if="${loginUser!=null}">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img th:src="${loginUser.headerUrl}" class="rounded-circle" style="width:30px;"/><!--img th:src="${loginUser.headerUrl}" 是当前登录的用户头像 -->
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="site/profile.html">个人主页</a>
<a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置</a>
<a class="dropdown-item text-center" th:href="@{/logout}">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary" th:utext="${loginUser.username}">nowcoder</span>
</div>
</li>
</ul>
运行:
登录之前:
登录成功以后:
总之,preHandle在请求开始之初,通过凭证找到用户,并且把用户暂存到HostHolder。
什么时候需要去用user?
在模板引擎之前,