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

news2024/11/15 6:59:44

实现退出功能

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

数据访问层

不用写了,已经有了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中的方法
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("preHandle: " + handler.toString());
        return true;
    }
    //在请求处理之后调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle: " + handler.toString());
    }
    //在模板引擎之后调用
    @Override
    public 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 {
    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    //重写preHandle方法
    @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());
                //在本次请求中持有用户,把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方法:
  @Override
  public 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.png
        User 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 {
    @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;
    }
}

  • 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.coloradmin.cn/o/1527601.html

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

相关文章

opc客户端

支持opc&#xff0c;da&#xff0c;ua通信&#xff08;匿名登陆及用户登陆&#xff09; 支持批量节点数据监听&#xff0c;当数据有变化时更新 支持单个节点读取和写入 KeepServer做为modbus server keepserver通信配置 https://blog.csdn.net/xiaochenXIHUA/article/detail…

【Qt问题】使用QSlider创建滑块小部件无法显示

问题描述&#xff1a; 使用QSlider创建滑块小部件用于音量按钮的时候&#xff0c;无法显示&#xff0c;很奇怪&#xff0c;怎么都不显示 一直是这个效果&#xff0c;运行都没问题&#xff0c;但是就是不出现。 一直解决不了&#xff0c;最后我在无意中&#xff0c;在主程序中…

【开发】Redis 的理解与数据存储格式

目录 相关传送门 1. NOSQL和关系型数据库比较 2. 主流的NOSQL产品 3. Redis的理解 4. redis数据存储格式 4.1 String 4.2 Hash 4.3 List 4.4 Set 4.5. sorted_set 注&#xff1a;手机端浏览本文章可能会出现 “目录”无法有效展示的情况&#xff0c;请谅解&#xf…

2、RabbitMQ_安装

RabbitMQ安装文档 RabbitMQ官网下载地址&#xff1a;https://www.rabbitmq.com/download.html 1.安装依赖 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel tk tc x…

【算法与数据结构】二叉树(前中后)序遍历

文章目录 &#x1f4dd;前言&#x1f320; 创建简单二叉树&#x1f309;二叉树的三种遍历&#x1f320;前序&#x1f309;中序遍历 &#x1f320;后序遍历 &#x1f320;二叉树节点个数&#x1f309;二叉树节点个数注意点 &#x1f6a9;总结 &#x1f4dd;前言 一棵二叉树是结…

为什么选择 Flink 做实时处理

优质博文&#xff1a;IT-BLOG-CN 为什么选择 Flink 【1】流数据更真实地反映了我们的生活方式&#xff08;实时聊天&#xff09;&#xff1b; 【2】传统的数据架构是基于有限数据集的&#xff08;Spark 是基于微批次数据处理&#xff09;&#xff1b; 【3】我们的目标&#xf…

C语言-memset(改变数值函数)

memset&#xff08;改变数值函数&#xff09; 函数的语法 &#xff08;第几个元素&#xff0c;改变成什么元素&#xff0c;几个字节&#xff09; memset函数是C语言标准库函数之一&#xff0c;用于将内存中的某一块区域全部设置为某个特定的值。它定义在<string.h>头文…

MyBatisPlus 之四:MP 的乐观锁和逻辑删除、分组、排序、链式的实现步骤

乐观锁 乐观锁是相对悲观锁而言的&#xff0c;乐观锁假设数据一般情况不会造成冲突&#xff0c;所以在数据进行提交更新的时候&#xff0c;才会正式对数据的冲突与否进行检测&#xff0c;如果冲突&#xff0c;则返回给用户异常信息&#xff0c;让用户决定如何去做。 乐观锁适用…

服务器数据恢复—光纤环境互斥不当导致存储VMFS卷损坏的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某公司的信息管理平台&#xff0c;通过3台虚拟机共享了一台存储设备供企业内部使用&#xff0c;存储设备中存放了公司内部重要的数据文件。 由于业务增长的需要&#xff0c;管理员又在这个存储网络上连接了一台Windows server服务器&a…

供应链投毒预警 | 开源供应链投毒202402月报发布啦

概述 悬镜供应链安全情报中心通过持续监测全网主流开源软件仓库&#xff0c;结合程序动静态分析方式对潜在风险的开源组件包进行动态跟踪和捕获&#xff0c;发现大量的开源组件恶意包投毒攻击事件。在2024年2月份&#xff0c;悬镜供应链安全情报中心在NPM官方仓库&#xff08;…

uniapp可视范围高度 - 用户屏幕可操作的屏幕高度 - 适用于APP、H5@公众号、纯H5@Chrome

可视范围高度 let heightPx uni.getWindowInfo().windowHeight uni.getWindowInfo().windowTop 官方手册 uni.getWindowInfo() | uni-app官网uni-app,uniCloud,serverless,uni.getWindowInfo()https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html 实测数据 uni.ge…

【C++初阶】第七站:string类的初识(万字详解、细节拉满)

前言&#xff1a; &#x1f4cd;本文知识点&#xff1a;string的初识 本专栏&#xff1a;C 目录 一、什么是STL 二、STL的六大组件 三、STL的缺陷 四、为什么学习string类&#xff1f; 五、标准库中的string类 1、string类(了解) 2、string类的常用接口说明&#xff08;…

2023新版mapinfo美化电子地图 新版2013Arcgis shp电子地图 下载

2023新版MapInfo和电子地图美化&#xff0c;以及2013版ArcGIS的SHP电子地图设计&#xff0c;是地理信息系统&#xff08;GIS&#xff09;领域中的两个重要话题。下面将分别对这两个主题进行描述。 样图&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1WB4AGsycyBGagVq5…

Python之Web开发中级教程----Django站点管理

Python之Web开发中级教程----Django站点管理 网站的开发分为两部分&#xff1a;内容发布和公共访问 内容发布是由网站的管理员负责查看、添加、修改、删除数据 Django能够根据定义的模型类自动地生成管理模块 使用Django的管理模块, 需要按照如下步骤操作 : 1.管理界面本地…

21年电赛-送药小车—基于OpenMV的寻迹+检测路口+数字识别(多模版匹配)(附代码)

我们花费了四天时间打了一场21年的电赛改编题——智能送药小车。虽然结果不尽人意&#xff0c;但这是我学习32以来第一次正式的打比赛&#xff0c;对我来说要学习的东西&#xff0c;所增长的经验真的特别多&#xff08;虽然基本上都是学长在出力~&#xff09;下来我就把关于这次…

上位机图像处理和嵌入式模块部署(qmacvisual畸变矫正)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 大部分同学在开始做计算机图像的时候&#xff0c;是没有意识到畸变矫正这个问题的。当然不仅仅是畸变矫正&#xff0c;很多同学还会忽略光源的问题…

qt使用Windows经典风格,以使QTreeView或QTreeWidge有节点线或加号

没有使用Windows经典风格的QTreeView或QTreeWidget显示如下&#xff1a; 使用Windows经典风格的QTreeView或QTreeWidget显示如下&#xff1a; 树展开时&#xff1a; 树未展开时&#xff1a; 可以看到&#xff1a; 未使用Windows经典风格时&#xff0c;QTreeView或QTreeWidget…

Memcached-分布式内存对象缓存系统

目录 一、NoSQL 介绍 二、Memcached 1、Memcached 介绍 1.1 Memcached 概念 1.2 Memcached 特性 1.3 Memcached 和 Redis 区别 1.4 Memcached 工作机制 1.4.1 内存分配机制 1.4.2 懒惰期 Lazy Expiration 1.4.3 LRU&#xff08;最近最少使用算法&#xff09; 1.4.4…

孙溟㠭于北京大学北大书店现场创作

孙溟㠭于北京大学北大书店现场创作篆刻作品 孙溟㠭北大书店现场创作 孙溟㠭于北京大学北大书店展览期间现场创作 孙溟㠭北京大学篆刻展现场创作 图文/氿波

【C语言】守护进程(daemon)的输出到一个文本文件

一、常用的守护进程函数 void daemonize () {//deamonizepid_t pid fork();if( pid > 0 ){ //parent exitexit(0);}//child continuesetsid();chdir("/");close(0);open("/dev/null", O_RDWR);//no env debugif(!getenv("debug")){cl…