Day21:实现退出功能、开发账号设置、检查登录状态

2024-03-19 03:36

本文主要是介绍Day21:实现退出功能、开发账号设置、检查登录状态,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

实现退出功能

  • 将登录凭证修改为失效状态。
  • 跳转至网站首页。

数据访问层

不用写了,已经有了updateStatus方法;

业务层

UserService

    public void logout(String ticket) {loginTicketMapper.updateStatus(ticket, 1);}

Controller层

@RequestMapping(path = "/logout", method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket) {userService.logout(ticket);return "redirect:/login";
}
  • 重定向默认是get请求

静态html修改index.html

<li class="nav-item ml-3 btn-group-vertical dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><img src="http://images.nowcoder.com/head/1t.png" 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">nowcoder</span></div>
</li>

把这里的退出登录改成th:href;

  • 不明原因这里的下拉菜单没办法点开!!!先放个屁股,之后找到bug了再看。

显示登录信息

  • 拦截器示例
    • 定义拦截器,实现HandlerInterceptor
    • 配置拦截器,为它指定拦截、排除的路径
  • 拦截器应用
    • 在请求开始时查询登录用户
    • 在本次请求中持有用户数据
    • 在模板视图上显示用户数据
    • 在请求结束时清理用户数据

拦截器示例

  • 拦截浏览器访问的请求,在请求开始/结束插入代码从而批量解决共有任务。
  1. 在controller中新建一个包Interceptor,新建AlphaInterceptor类:
@Controller
public class AlphaInterceptor implements HandlerInterceptor {
//在请求处理之前调用private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);//返回值决定是否继续执行Controller中的方法@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {logger.debug("preHandle: " + handler.toString());return true;}//在请求处理之后调用@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle: " + handler.toString());}//在模板引擎之后调用@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion: " + handler.toString());}
}
  • 需要实现HandlerInterceptor接口,但该接口中方法都是Default,不一定全部都需要重写。
  1. 编写一个配置类配置:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowired private AlphaInterceptor alphaInterceptor;public void addInterceptors(InterceptorRegistry registry) {//拦截除了css,js,png,jpg,jpeg之外的所有请求//只拦截注册和登录请求//为什么是/**/*:static目录下所有目录下的css,js,png,jpg,jpeg文件registry.addInterceptor(alphaInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg").addPathPatterns("/register", "/login");}}
  • 拦截除了css,js,png,jpg,jpeg之外的所有请求
  • 只拦截register和login请求
  • 为什么是/**/*:static目录下所有目录下的css,js,png,jpg,jpeg文件
  1. 测试

访问对应路径,看到以下输出:

image

image

拦截器应用

一次已登录的用户的请求过程(每次请求都有,故应该用拦截器进行复用)

image

  1. 首先封装一个util类获取对应key(ticket)的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){//一个cookie都没有for(Cookie cookie : cookies){if(cookie.getName().equals(name)){return cookie.getValue();}}}return null;}
}
  1. 为UserService添加通过ticket取user的方法:
    public LoginTicket findLoginTicket(String ticket) {return loginTicketMapper.selectByTicket(ticket);}
  1. 编写LoginTicketInterceptor拦截器(见下)
  2. 编写HostHolder确保每个用户线程独立互不干扰:
@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();}}

在请求开始时查询登录用户

  • 重写preHandler方法:
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {@Autowiredprivate UserService userService;@Autowiredprivate HostHolder hostHolder;//重写preHandle方法@Overridepublic 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());//在本次请求中持有用户,把user暂存一下hostHolder.setUser(user);//hostHolder相当于为当前线程的user提供一个临时的容器}}return true;}
...
}

在本次请求中持有用户数据

hostHolder.setUser(user);

在模板视图上显示用户数据

  • 重写PostHandler方法:
@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);}
}
  • modelAndView.addObject(“loginUser”, user);,在之后的模版中就可以使用loginUser了。

在请求结束时清理用户数据

  • 重写afterCompletion方法:
  @Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清除用户hostHolder.clear();}
  1. 修改html,使一些元素在未登录和登录时的显示情况不同:
<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></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" th:if="${loginUser!=null}"><a class="nav-link" th:href="@{/logout}">退出</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>

开发账号设置

image

请求:必须是POST请求

  • 表单:enctype=“multipart/form-data”
  • Spring MVC:通过 MultipartFile类上传文件。

访问账号设置页面

controller层

设置一个新的UserController:

@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping(path = "/setting",method = RequestMethod.GET)public String getSettingPage() {return "/site/setting";}}

修改setting,html

  • 除了静态改动态的必要设置外,
  • 把index.html的链接练到/setting来。
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}"><a class="nav-link" th:href="@{/user/setting}">账号设置</a>
</li>

上传头像

  1. 在配置文件中配置上传文件的存储路径
community.path.upload = /Users/iris/Desktop/community/upload
  • 这里必须手动创建upload目录!否则之后上传的时候会不存在!

数据访问层

无。

业务层

  1. 更新headerUrl。
public int updateHeader(int userId, String headerUrl) {return userMapper.updateHeader(userId, headerUrl);
}

和Controller层

@RequestMapping(path = "/upload",method = RequestMethod.POST)public String uploadHeader(MultipartFile headerImage, Model model) {if(headerImage == null) {logger.error("上传文件为空");model.addAttribute("error","您还没有选择图片");return "/site/setting";}//不能直接上传到服务器的文件夹中,因为服务器可能有多个用户,文件名可能重复String fileName = headerImage.getOriginalFilename();//原始文件名String suffix = fileName.substring(fileName.lastIndexOf("."));//文件后缀if(suffix == null) {logger.error("文件格式不正确");return "redirect:/user/setting";}//生成随机文件名fileName = CommunityUtil.generateUUID() + suffix;//确定文件存放路径java.io.File dest = new java.io.File(uploadPath + "/" + fileName);try {headerImage.transferTo(dest);} catch (IOException e) {logger.error("上传文件失败" + e.getMessage());throw new RuntimeException("上传文件失败,服务器发生异常",e);}//更新当前用户的头像路径(web访问路径)//http://localhost:8080/community/user/header/xxx.pngUser user = hostHolder.getUser();String headerUrl = domain + contextPath + "/user/header/" + fileName;userService.updateHeader(user.getId(),headerUrl);return "redirect:/index";}

设置头像

真正的使用IO柳得到头像,这里注意为了解耦也在Controller里面写了:

@RequestMapping(path = "/header/{fileName}",method = RequestMethod.GET)public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {//返回值是void:因为返回的是图片等二进制数据//服务器存放路径fileName = uploadPath + "/" + fileName;//文件后缀String suffix = fileName.substring(fileName.lastIndexOf("."));//响应图片response.setContentType("image/" + suffix);try(//放在try()中的流会自动关闭OutputStream os = response.getOutputStream();FileInputStream fis = new FileInputStream(fileName);) {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());}}
  • 返回值是void,因此返回的是图片等二进制数据;
  • 放在try括号里的流会自动关闭,java8开始;
  • 要用io写入二进制数据,建立缓冲区
  • 使用PathVarible注解取filename的值赋给filename变量,从而填补@RequestMapping

修改静态setting.html为动态

<form class="mt-5" method="post" enctype="multipart/form-data" th:action="@{/user/upload}"><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' : '')}"id="head-image" name="headerImage" 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>
  • 这个拼接字符串永远做不对!!!

修改密码

检查登录状态

  • 目的:确保用户在未登录情况下不能通过特定的url访问界面。

使用拦截器

  • 在方法前标注自定义注解
  • 拦截所有请求,只处理带有该注解的方法

自定义注解(重要)

  • 常用的元注解:

@Target、@Retention、@Document、@Inherited - 如何读取注解:

Method.getDeclaredAnnotations​() Method.getAnnotation​(Class annotationClass)

  • 加注解就拦截,不加注解就不拦截。
  1. 新建一个包annotation,在其中创建一个注解LoginRequired,使用元注解修饰:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {//什么都不用写
}
  • target表示该注解只能修饰method
  • retention表示注解在runtime运行时也存在;
  1. 创建注解的拦截器:
@Component
public class LoginRequireInterceptor implements HandlerInterceptor {@Autowiredprivate HostHolder hostHolder;@Overridepublic 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;}
}
  • HandlerMethod handlerMethod = (HandlerMethod) handler;传进来的handler是不是method;
  • LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);:判断注解是否存在,不在返回null;
  • 判断如果注解存在,用户是否登录。
  1. 配置拦截器,不拦截css等文件节省资源:
public void addInterceptors(InterceptorRegistry registry) {//拦截除了css,js,png,jpg,jpeg之外的所有请求//只拦截注册和登录请求。。。registry.addInterceptor(loginRequireInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");}
  1. 给需要登录的页面加上@LoginRequired注解
@LoginRequired@RequestMapping(path = "/setting",method = RequestMethod.GET)public String getSettingPage() {return "/site/setting";}@LoginRequired@RequestMapping(path = "/upload",method = RequestMethod.POST)
public String uploadHeader(MultipartFile headerImage, Model model) {....}

这篇关于Day21:实现退出功能、开发账号设置、检查登录状态的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/824656

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi