(仿牛客论坛项目)05 - 显示登录信息 - 账号设置 - 检查登录状态


前言

  1. 之前 SpringMVC 中已经有过关于 拦截器的讲解和配置,(尚硅谷)2021 版 SpringMVC 教程笔记,这里简单带过。

1、显示登录信息

1.1 拦截器示例

  1. 定义拦截器,实现 HandlerInterceptor;
@Component
public class HelloInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(HelloInterceptor.class);

    // 在Controller之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("preHandle: " + handler.toString());
        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 {
        logger.debug("afterCompletion: " + handler.toString());
    }
}
  1. 配置拦截器,为它指定拦截、排除的路径;
  2. 配置 MVC 的时候要实现 WebMvcConfigurer 这个类才可以;
  3. 将拦截器注入;
  4. 重写 addInterceptors 方法
  • 将拦截器添加进来: registry.addInterceptor
  • 排除所有静态资源:registry.excludePathPatterns
  • 设置拦截哪些路径:registry.addPathPatterns
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private HelloInterceptor helloInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(helloInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .addPathPatterns("/register", "/login");
    }
}

测试一下是否拦截成功,index 页面不拦截,登录、注册页面被拦截:

在这里插入图片描述

2.2 拦截器应用(使用线程隔离批量处理用户请求)

  • 在请求开始时查询登录用户
  • 在本次请求中持有用户数据
  • 在模板视图上显示用户数据
  • 在请求结束时清理用户数据

在这里插入图片描述

2.2.1 新建 LoginTicketInterceptor 拦截器

  1. 将从 cookie 中获取特定信息的方法包装成一个工具类;
public class CookieUtil {
    public static String getValue(HttpServletRequest request, String name) {
        if (request == null || name == null) {
            throw new IllegalArgumentException("参数为空!");
        }
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
}
  • request.getCookies();返回的是所有 cookie 信息,我们要从中取我们想要的信息。
  1. 实现 prehandle 方法:
  • 通过 cookie 得到 ticket 信息,用户登录之后会重定向到首页,这时我们可以从第二次请求中获取用户信息
  • 在 prehandle 中只能通过请求 request 来获取 cookie;
  • 如果 ticket 不为空,调用 UserService 层去获取该凭证对应的所有登录信息(UserService 层要加一个方法);
  • 检查凭证是否有效:LoginTicket 不为空,状态码为0,且有效时长没有超时的情况是有效的,调用 UserService 层查询 user 数据;
  • 本次请求中持有用户:
//用户登录成功之后重定向到首页,这时我们可以获取用户信息,让线程持有当前用户
@Override
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())) {
            // 根据凭证查询用户
            User user = userService.findUserById(loginTicket.getUserId());
            // 在本次请求中持有用户
            hostHolder.setUser(user);
        }
    }
    return true;
}

解释:

  • 线程隔离存放用户信息,实现 HostHolde r类,使用 ThreadLocal 模拟 session 存放用户信息;
  • ThreadLocal 以线程为 key 实现存取值;
  • 将 HostHolder 注入,调用其中的 setUser 方法,将用户信息存到某一线程对应的map容器中;
/**
 * 持有用户信息,用于代替session对象.
 */
@Component
public class HostHolder {
    private ThreadLocal<User> users = new ThreadLocal<>();

    public void setUser(User user) {
        users.set(user);
    }

    public User getUser() {
        return users.get();
    }

    public void clear() {
        users.remove();
    }
}
  1. 实现 postHandler 方法:
  • 调用 HostHolder 取 user 值,将当前用户存放在模板引擎中,用来显示用户信息;
//将持有的用户信息保存到模板引擎中
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    User user = hostHolder.getUser();
    if (user != null && modelAndView != null) {
        modelAndView.addObject("loginUser", user);
    }
}
  1. 实现afterCompletion方法:
  • 清掉当前用户占用的线程
//模板渲染结束后,即当前请求结束后清理县城内存放的用户
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    hostHolder.clear();
}

代码总结:

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    //用户登录成功之后重定向到首页,这时我们可以获取用户信息,让线程持有当前用户
    @Override
    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())) {
                // 根据凭证查询用户
                User user = userService.findUserById(loginTicket.getUserId());
                // 在本次请求中持有用户
                hostHolder.setUser(user);
            }
        }

        return true;
    }

    //将持有的用户信息保存到模板引擎中
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        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();
    }
}

2.2.2 UserService 层

  1. 实现通过 Ticket 查询用户登录凭证信息;
@Override
public LoginTicket findLoginTicket(String ticket) {
    return loginTicketMapper.selectByTicket(ticket);
}

2.2.3 WebMvcConfig 中配置拦截器

  1. 添加当前拦截器,同时排除所有静态页面(减少拦截器拦截的请求数量,静态页面也会发请求):
@Autowired
private LoginTicketInterceptor loginTicketInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginTicketInterceptor)
            .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}

2.2.4 修改 index.html 页面

  1. 消息:登录状态下显示,th:if="${loginUser!=null}"
  2. 登录、注册:登录了不显示,th:if="${loginUser==null}"
  3. 用户头像、用户名动态显示:th:src="${loginUser.headerUrl}"th:utext="${loginUser.username}"
  4. 修改退出登录超链接:th:href="@{/logout}"
<ul class="navbar-nav mr-auto">
	<li class="nav-item ml-3 btn-group-vertical">
		<a class="nav-link" th:href="@{/index}">首页</a>
	</li>
	<li class="nav-item ml-3 btn-group-vertical" th:if="${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}">
		<a class="nav-link" th:href="@{/register}">注册</a>
	</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;"/>
		</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" href="site/setting.html">账号设置</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>

2.2.5 测试页面

  1. 登陆之前:

在这里插入图片描述

  1. 登录之后:

在这里插入图片描述

  1. 点击退出登录

在这里插入图片描述

2、账号设置

2.1 上传文件

在这里插入图片描述

在 Controller 中处理上传文件的步骤:

  1. 判断控制和文件类型是否正确;
  2. 随机生成文件名 + 后缀;
  3. 确定文件上传目录,并判断该目录是否存在,不存在则创建目录;
  4. 最终确定文件上传路径为 上传目录 + / + 文件名;
  5. 利用 transferTo 方法上传文件。

在这里插入图片描述

2.2 新建 UserController 类

  1. 实现 getSettingPage() 方法;
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/setting",method = RequestMethod.GET)
    public String getSettingPage(){
        return "site/setting";
    }
}

2.3 修改 setting.html 页面

  1. 引入 thymeleaf 文件
  2. 静态资源路径修改
  3. 头部代码复用:th:replace="index::header"

2.4 修改 index.html 页面

  1. 将账号设置的超链接进行修改:
<a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置</a>

2.5 测试页面

在这里插入图片描述

2.6 配置资源上传的路径(上传头像)

community.path.upload=d:/work/data/upload
  • 我们只在 Controller 中处理上传文件的逻辑,而不调用 service 层,因为 MultipartFile 是表现层 SpringMVC 的一个组件,不要在 service 层调用;

2.7 UserService 层

  1. 实现更新用户头像方法,即根据用户的 userId 更新用户头像的 url 地址;
@Override
public int updateHeader(int userId, String headerUrl) {
    return userMapper.updateHeader(userId,headerUrl);
}

2.8 UserController 层(客户端到服务器)

  1. 判断文件是否为空,或者文件格式是否正确;

  2. 生成随机文件名: 使用生成随机字符串工具类生成 随机字符+后缀 作为文件名

  3. 确定文件目录是否为空,为空则创建目录;

  4. 确定文件存放的位置:新建一个 file,其中是上传路径+/+文件名字

  5. 存储文件:使用 transferTo(File file) 方法传到服务器中;

  6. 更新当前用户的头像的路径(web访问路径):http://localhost:8080/community/user/header/xxx.png, 调用 UserService 层更新数据库中用户头像的 url 地址;

  7. 重定向到首页。

@RequestMapping(value = "/setting", method = RequestMethod.POST)
public String uploadHeader(
        @RequestParam(value = "headerImage") MultipartFile photo, Model model) {
    //空值处理
    if (photo == null || photo.isEmpty()) {
        model.addAttribute("error", "您还没有选择图片!");
        return "/site/setting";
    }
    //文件检测是否合格
    String fileName = photo.getName();
    String suffix = fileName.substring(fileName.lastIndexOf('.') + 1);
    if (StringUtils.isBlank(suffix)) {
        model.addAttribute("error", "文件的格式不正确!");
        return "/site/setting";
    }
    //生成随机文件名
    fileName = CommunityUtil.generateUUID() + suffix;
    //确认文件上传路径目录是否为空
    File dir = new File(uploadPath);
    if (!dir.exists()){
        dir.mkdir();
    }
    //确定图片在服务器中的存放路径
    File dest = new File(uploadPath + File.separator + fileName);
    try {
        //上传图片
        photo.transferTo(dest);
    } catch (IOException e) {
        logger.error("上传文件失败: " + e.getMessage());
        throw new RuntimeException("上传文件失败,服务器发生异常!", e);
    }

    // 更新当前用户的头像的路径(web访问路径)
    // http://localhost:8080/community/user/header/xxx.png
    User user = hostHolder.getUser();
    String headerUrl = domain + contextPath + "/user/header/" + fileName;
    userService.updateHeader(user.getId(), headerUrl);

    //重定向到首页
    return "redirect:/index";
}

2.9 UserController 层:获取头像(服务器响应给客户端)

  1. 实现获取头像方法,从图片在服务器的路径中读取图片(字节输入流),使用 response 获取字节输出流向客户端写文件;
  2. 设置相应类型为:setContentType=("image/"+suffix);
  3. 手动关闭我们自己创建的输入流(java7 中在 try 后面的小括号中写上需要关闭的资源,前提是要有 close 方法);通过 response 获取到的输出流,SpringMVC 会自动帮我们进行关闭操作。
//给客户端显示新头像
@RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)
public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
    // 服务器存放路径
    fileName = uploadPath + File.separator + fileName;
    // 文件后缀(要作为给web的响应内容,所以不可以带.)
    String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
    // 响应图片
    response.setContentType("image/" + suffix);
    try (
            //通过输入流读取文件信息
            FileInputStream fis = new FileInputStream(fileName);
            //通过输出流写到网页上
            OutputStream os = response.getOutputStream();
    ) {
        //图片可能比较大,缓冲输出,声明一个缓冲区,游标
        byte[] buffer = new byte[1024];
        int b = 0;
        while ((b = fis.read(buffer)) != -1) {
            os.write(buffer, 0, b);
        }
    } catch (IOException e) {
        logger.error("读取头像失败: " + e.getMessage());
    }
}

2.10 处理 settings.html 页面

  1. 上传头像的表单修改
  2. 手动处理如果没有文件为空的错误信息显示
<!-- 上传头像 -->
<h6 class="text-left text-info border-bottom pb-2">上传头像</h6>
<form class="mt-5" th:action="@{/user/setting}" enctype="multipart/form-data" method="post">
	<div class="form-group row mt-4">
		<label for="head-image" class="col-sm-2 col-form-label text-right">选择头像:</label>
		<div class="col-sm-10">
			<div class="custom-file">
				<input type="file" th:class="|custom-file-input ${error!=null?'is-invalid':''}|"
					   name="headerImage"
					   id="head-image" lang="es" required="">
				<label class="custom-file-label" for="head-image" data-browse="文件">选择一张图片</label>
				<div class="invalid-feedback" th:text="${error}">
					请上传图片!
				</div>
			</div>
		</div>
	</div>
	<div class="form-group row mt-4">
		<div class="col-sm-2"></div>
		<div class="col-sm-10 text-center">
			<button type="submit" class="btn btn-info text-white form-control">立即上传</button>
		</div>
	</div>
</form>

2.11 测试页面

  1. 不上传头像(前端逻辑也过不去)

在这里插入图片描述

  1. 上传不带文件后缀的文件:

在这里插入图片描述

  1. 正确上传头像:

在这里插入图片描述

2.10 错误排查

  1. 必须在请求的 form 表单中设置上传格式为:enctype="multipart/form-data",否则的话报错;
Current request is not a multipart request
  1. 获取上传的图片的文件名字必须使用 photo.getOriginalFilename(); 方法,这样才是获取你上传的文件的名字,否则的话是不对的;

  2. 记得在 UserController 上面加上 @RequestMapping("/user"),代表在上下文路径下多一层目录,同时表单请求和 index.html 文件中的账号设置请求都要在请求中添加 /user 这一层路径:

<form class="mt-5" th:action="@{/user/setting}" enctype="multipart/form-data" method="post">
  1. 上传不带文件后缀的文件时报错,表示文件没有后缀名的时候 lastIndexOf 这个方法默认为 -1 ,这时使用 substring 方法来进行字符串截取就会报索引越界异常:
java.lang.StringIndexOutOfBoundsException: begin -1, end 23, length 23

将 UserController 修改如下:

int separator = 0;
String suffix = fileName.substring(separator = fileName.lastIndexOf('.')==-1?fileName.length():separator);    

3、账号设置(修改密码)

3.1 UserService 层

  1. 实现修改密码功能:
  • 空值处理:输入旧密码不能为空,输入的新密码不能为空,输入的新密码的确认密码不能为空;
  • 验证状态:
    • 判断两次输入的新密码是否一致,验证输入的旧密码是否正确;
    • 将用户输入的旧密码使用 MD5 加密算法进行加密得到加密后的密码,将这个密码和当前用户的密码进行比较,如果不一致,代表输入的密码错误
  • 使登录凭证失效:
    • 调用 DAO 层修改登录凭证状态码为 1 ;
@Override
public Map<String, Object> updatePassword(User user,String oldPassword, String newPassword, String confirmPassword,String ticket) {
    Map<String, Object> map = new HashMap<>();
    //空值处理
    if (StringUtils.isBlank(user.getPassword())) {
        map.put("oldPasswordMsg", "请输入您原来的密码!");
        return map;
    }
    if (StringUtils.isBlank(newPassword)) {
        map.put("newPasswordMsg", "请输入您的新密码!");
        return map;
    }
    if (StringUtils.isBlank(confirmPassword)) {
        map.put("confirmPasswordMsg", "请再次输入您的新密码!");
        return map;
    }
    //两次输入的新密码是否一致
    if (!newPassword.equals(confirmPassword)) {
        map.put("confirmPasswordMsg", "两次输入密码不一致!");
        return map;
    }
    //验证旧密码是否正确,注册的时候在末尾加盐,这里也一样
    oldPassword = CommunityUtil.md5(oldPassword + user.getSalt());
    if (!user.getPassword().equals(oldPassword)){
        map.put("oldPasswordMsg", "您输入的密码不正确!");
        return map;
    }
    //修改密码
    newPassword = CommunityUtil.md5(newPassword + user.getSalt());
    userMapper.updatePassword(user.getId(),newPassword);
    //修改用户登录状态,修改密码之后让用户重新登陆,所以要将原来的登录状态改为退出
    loginTicketMapper.updateStatus(ticket, 1);

    return map;
}

3.2 UserController 层

  1. 实现修改密码的功能:
  2. 从当前线程获取用户信息
  3. 调用 UserService 层进行密码的校验;
  4. 根据返回的 map 集合信息判断要跳转的页面,并且显示错误信息,或者修改成功的信息。
//修改密码
@RequestMapping(path = "/update", method = RequestMethod.POST)
public String updatePassword(Model model, String oldPassword,
                             String newPassword, String confirmPassword,
                             @CookieValue("ticket") String ticket
) {
    User user = hostHolder.getUser();
    Map<String, Object> map = userService.updatePassword(user, oldPassword, newPassword, confirmPassword, ticket);
    if (map == null || map.isEmpty()) {
        model.addAttribute("msg", "密码修改成功,请重新登录!");
        model.addAttribute("target", "/login");
        return "site/operate-result";
    }else {
        model.addAttribute("oldPasswordMsg",map.get("oldPasswordMsg"));
        model.addAttribute("newPasswordMsg",map.get("newPasswordMsg"));
        model.addAttribute("confirmPasswordMsg",map.get("confirmPasswordMsg"));
        return "/site/setting";
    }
}

3.3 修改 setting.html 页面:

  1. 首先是前端对两次输入密码不一致的判定,这里参考 register.html 注册页面的验证方式,新增了一个 setting.js 文件,实现验证两次密码不一致时的错误信息显示:
$(function(){
    $("form").submit(check_data);
    $("input").focus(clear_error);
});

function check_data() {
    var pwd1 = $("#new-password").val();
    var pwd2 = $("#confirm-password").val();
    if(pwd1 != pwd2) {
        $("#confirm-password").addClass("is-invalid");
        return false;
    }
    return true;
}

function clear_error() {
    $(this).removeClass("is-invalid");
}
  1. 在 setting.html 最后引入该文件:
<script th:src="@{/js/setting.js}"></script>
  1. 修改 form 表单的方法和行为:th:action="@{/user/update}" method="post"

  2. 修改所有输入文本框的 name 属性,必须和 UserController 中形参定义的参数名称一致,否则接收不到 form 表单提交的值;

  3. 错误信息展示:

    1. 用户输入的值还显示在页面上,可以从 request 中取值:th:value="${param.oldPassword}"
    2. 显示错误信息:th:text="${oldPasswordMsg}"
    3. 显示错误信息样式:th:class="|form-control ${oldPasswordMsg!=null?oldPasswordMsg}|"
<!-- 修改密码 -->
<h6 class="text-left text-info border-bottom pb-2 mt-5">修改密码</h6>
<form class="mt-5" th:action="@{/user/update}" method="post">
	<div class="form-group row mt-4">
		<label for="old-password" class="col-sm-2 col-form-label text-right">原密码:</label>
		<div class="col-sm-10">
			<input type="password" th:class="|form-control ${oldPasswordMsg!=null?oldPasswordMsg}|"
				   name="oldPassword" th:value="${param.oldPassword}"
				   id="old-password" placeholder="请输入原始密码!" required>
			<div class="invalid-feedback" th:text="${oldPasswordMsg}">
				密码长度不能小于8位!
			</div>							
		</div>
	</div>
	<div class="form-group row mt-4">
		<label for="new-password" class="col-sm-2 col-form-label text-right">新密码:</label>
		<div class="col-sm-10">
			<input type="password" class="form-control"
				   name="newPassword" th:value="${param.newPassword}"
				   id="new-password" placeholder="请输入新的密码!" required>
			<div class="invalid-feedback" th:text="${newPasswordMsg}">
				密码长度不能小于8位!
			</div>							
		</div>
	</div>
	<div class="form-group row mt-4">
		<label for="confirm-password" class="col-sm-2 col-form-label text-right">确认密码:</label>
		<div class="col-sm-10">
			<input type="password" class="form-control"
				   name="confirmPassword" th:value="${param.confirmPassword}"
				   id="confirm-password" placeholder="再次输入新密码!" required>
			<div class="invalid-feedback" th:text="${confirmPasswordMsg}">
				两次输入的密码不一致!
			</div>								
		</div>
	</div>				
	<div class="form-group row mt-4">
		<div class="col-sm-2"></div>
		<div class="col-sm-10 text-center">
			<button type="submit" class="btn btn-info text-white form-control">立即保存</button>
		</div>
	</div>
</form>

3.4 测试页面

  1. 输入为空值的时候(前端实现了不允许为空值的情况):

在这里插入图片描述

  1. 输入新旧密码不一致的时候:

在这里插入图片描述

  1. 输入旧密码不正确的时候(虽然跳回到了设置页面,但是没有提示错误信息,检查页面接收到的响应代码没有问题,不知道为什么不显示):

在这里插入图片描述

下面这个是上传头像文件格式有误时候的前端代码,页面提示错误信息显示是正常的(费解!!!!):

在这里插入图片描述

  1. 输入正确情况:

在这里插入图片描述

在这里插入图片描述

并且登录凭证状态修改为1,即失效了。

在这里插入图片描述

3.5 错误排查

  1. 旧密码输入不正确的时候跳回到设置页面,没有显示错误信息(问题未解决!!!!!!!!!!!)

4、检查登录状态

  1. 在用户没有登录的情况下,拦截一些请求,放置知道访问路径的人可以通过手动修改访问路径去访问一下页面;

在这里插入图片描述

  • 常用的元注解(主要定义前两个就可以了):
    @Target:定义在类、方法、属性等上面
    @Retention:编译时有效or运行时有效
    @Document:生成文档的时候要不要带这个注解
    @Inherited:子类要不要继承父类的这个注解

  • 如何读取注解:
    Method.getDeclaredAnnotations ():获取方法上的所有注解
    Method.getAnnotation (Class<T> annotationClass):尝试获取某个类型的注解

4.1 在 annotation 包下新建 LoginRequired 类

  1. 其中定义一个登录之后才能访问的页面的注解
  2. 给所有登录后才能访问的方法,加上这个自定义注解(所有关于用户设置的,都要在方法上方加上这个注解,但是获取用户头像不用这个注解,因为在首页的时候要显示所有帖子的用户头像)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {

}

4.2 新建 LoginRequiredInterceptor 类

  1. 注入 Hostholder,获取当前是否有用户信息
  2. 如果拦截到的是一个方法的话,将他转型
  3. 从方法上面取对应的自定义注解类型的注解
  4. 如果获取到了该注解,但是获取到的用户为空,我们就进行拦截,即返回false,同时强制重定向到登录页面
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {
    @Autowired
    private HostHolder hostHolder;

    //在请求前拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
            if (loginRequired != null && hostHolder.getUser() == null) {
                response.sendRedirect(request.getContextPath() + "/login");
                return false;
            }
        }
        return true;
    }
}

4.3 配置拦截器:

  1. 将当前拦截器注入
  2. 排除对静态资源的拦截
@Autowired
private LoginRequiredInterceptor loginRequiredInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginRequiredInterceptor)
            .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}

4.4 测试页面

  1. 当我试图访问修改用户信息界面时,自动跳转到了登陆页面:

在这里插入图片描述
在这里插入图片描述


总结

  1. 修改密码的错误信息回显没有效果。可能是前端哪里出问题了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值