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) {....}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/754053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python:filter过滤器

filter() 是 Python 中的一个内置函数&#xff0c;用于过滤序列&#xff0c;过滤掉不符合条件的元素&#xff0c;返回由符合条件元素组成的新列表。该函数接收两个参数&#xff0c;一个是函数&#xff0c;一个是序列&#xff0c;序列的每个元素作为参数传递给函数进行判定&…

电脑msvcp140_1.dll丢失的解决方法,总结5种可靠的方法

在日常使用电脑的过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“msvcp1401.dll丢失”。这个DLL文件是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序来说至关重要。这个错误通常会导致某些应用程序无…

摄影第一课

色彩 红色绿色黄色 红色蓝色洋红 蓝色绿色青色 冷暖色 摄影基础 选择合适的前景&#xff0c;增加照片层次感 测光拍摄&#xff0c;照片有亮和暗的地方&#xff0c;立体感更强 拍摄技巧 拍摄倒影 手机靠近水面&#xff0c;距离越近拍到的倒影越多适当降低曝光、获得更加准…

springboot 动漫周边商城的设计与实现

摘 要 二十一世纪我们的社会进入了信息时代&#xff0c;信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式对时间、地点的限制太多&#xff0c;而在线管理系统刚好能满足这些需求&#xff0c;在线管理系统突破了传统管理方式的局限性。于是本文针对这一…

6语言交易所/多语言交易所php源码/微盘PHP源码

6语言交易所PHP源码&#xff0c;简单测试了一下&#xff0c;功能基本都是正常的。 由于是在本地测试的运行环境的问题&#xff0c;K线接口有点问题&#xff0c;应该在正式环境下是OK的。 源码下载地址&#xff1a;6语言交易所/多语言交易所php源码/微盘PHP源码.zip 程序截图…

构建用户身份基础设施,推动新能源汽车高质量发展

随着市场进入智能电动汽车时代&#xff0c;车企们发现&#xff0c;在激烈竞争的市场中不断增长&#xff0c;并不是一件容易的事。《麻省理工科技评论》&#xff0c;前段时间写了一篇报道&#xff1a;中国是如何称霸电动汽车世界的&#xff1f;“过去两年&#xff0c;中国电动汽…

洛谷_P1152 欢乐的跳_python写法

思路&#xff1a; 这道题我用到了集合的互异性来判断这组数字是否满足条件我觉得是比较有效一点的。 data list(map(int,input().split())) data data[1:] l [i for i in range(1,len(data))] s [] for i in range(len(data)-1):s.append(abs(data[i] - data[i1]))if set(…

[python3] 设置多进程名称并且在ps命令中可见

Centos7 系统 setproctitle 是一个 Python 模块&#xff0c;用于设置进程标题&#xff08;process title&#xff09;。进程标题是在系统中用来标识进程的名字&#xff0c;通常会显示在系统级的进程管理工具&#xff08;如 ps 命令&#xff09;中。通过设置进程标题&#xff0c…

断言assert是什么?

assert是什么&#xff1f; assert断言&#xff0c;是一个被定义在<assert.h>头文件中的一个宏&#xff0c;而不是一个函数。 可以用来检查数据的合法性&#xff0c;但是频繁的调用极大影响了程序的性能&#xff0c;增加了额外的开销。可以通过#define NDEBUG来禁用asse…

阿里云-零基础入门NLP【基于机器学习的文本分类】

文章目录 学习过程赛题理解学习目标赛题数据数据标签评测指标解题思路TF-IDF介绍TF-IDF 机器学习分类器TF-IDF LinearSVCTF-IDF LGBMClassifier 学习过程 20年当时自身功底是比较零基础(会写些基础的Python[三个科学计算包]数据分析)&#xff0c;一开始看这块其实挺懵的&am…

分享一个不错的three.js开源项目

项目将three.js相关内容封装为相应库 很值得学习&#xff0c;可以模仿项目学习three.js vue-vite-three.js threejs-park: 基于vue3&#xff0c;threeJS智慧园区 threejs-park

JavaScript中的Hoisting

概要 本文在Javascript的Execution Context文章基础上&#xff0c;从代码执行的角度来谈谈变量提升&#xff0c;已经为什么let和const的变量不能进行变量提升。 代码分析 var 关键字定义的变量 下面的代码并不会报错&#xff0c;可以正常执行。 console.log(a) var a 0;代…

JS的一些方便遍历数组的API函数

这些函数有的时候在学到后面的内容会遇到&#xff0c;看一些前端的视频的时候突然出现这些函数看到有点懵&#xff0c;现在就整合一下&#xff0c;然后以后看到这些函数就知道是干什么的了 1、箭头函数 没学完js的必须得先了解箭头函数 //它两一样 function fn(){console.lo…

图文并茂!在Oracle VM VirtualBox上安装Ubuntu虚拟机的详细步骤指南

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

202303 CSP认证 | LDAP

LDAP 好好好&#xff0c;难度直线上升&#xff0c;是一道又有了字符串处理味道的第三题 第一把写官网40分&#xff0c;acwing TLE且只通过了一道数据…本文是自己这题奋斗过程 的一个记录 先贴个40分的代码&#xff1a; #include<bits/stdc.h> using namespace std; t…

Servlet两种配置

通过xml配置 <servlet><servlet-name>MyServlet</servlet-name><servlet-class>MyServlet</servlet-class> </servlet> <servlet-mapping><servlet-name>MyServlet</servlet-name><url-pattern>/MyServlet</ur…

U盘变本地磁盘?数据恢复有妙招!

一、U盘异变本地磁盘现象 在日常工作和生活中&#xff0c;U盘作为便携式的存储设备&#xff0c;广受大家喜爱。然而&#xff0c;有时我们会遇到一个奇怪的现象&#xff1a;原本应该显示为可移动磁盘的U盘&#xff0c;在插入电脑后却变成了“本地磁盘”。这种情况不仅让人困惑&…

论文解读之Attention-based Deep Multiple Instance Learning

前言 多实例学习是由监督学习演变而来的&#xff0c;我们都知道&#xff0c;监督学习在训练的时候是一个实例&#xff08;或者说一个样本、一条训练数据&#xff09;对应一个确定的标签。而多实例的特点就是&#xff0c;我们在训练的时候的输入是多个实例对应一个确定的标签&a…

JUC之CAS比较并交换

CAS 原子类引入 加入原子整型类的操作后&#xff0c;无锁化的操作 CAS 比较并交换 硬件级别的保证 源码分析 引出UnSafe类 UnSafe源码分析 unsafe中的do-while保证自旋 public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 this…