《尚品甄选》:后台系统——结合redis实现用户登录

文章目录

  • 一、统一结果实体类
  • 二、统一异常处理
  • 三、登录功能实现
  • 四、CORS解决跨域
  • 五、图片验证码
  • 六、登录校验功能实现
    • 6.1 拦截器开发
    • 6.2 拦截器注册
  • 七、ThreadLocal


在这里插入图片描述
要求: 用户输入正确的用户名、密码以及验证码,点击登录可以跳转到后台界面。未登录的用户或者登录过期的用户没有访问后台界面的权限。

一、统一结果实体类

尚品甄选项目中所有接口的返回值统一都会定义为Result。

@Data
@Schema(description = "响应结果实体类")
public class Result<T> {//返回码@Schema(description = "业务状态码")private Integer code;//返回消息@Schema(description = "响应消息")private String message;//返回数据@Schema(description = "业务数据")private T data;private Result() {}// 返回数据public static <T> Result<T> build(T body, Integer code, String message) {Result<T> result = new Result<>();result.setData(body);result.setCode(code);result.setMessage(message);return result;}// 通过枚举构造Result对象public static <T> Result build(T body , ResultCodeEnum resultCodeEnum) {return build(body , resultCodeEnum.getCode() , resultCodeEnum.getMessage()) ;}
}

为了简化Result对象的构造,可以定义一个枚举类,在该枚举类中定义对应的枚举项来封装code、message的信息,如下所示:

@Getter
public enum ResultCodeEnum {SUCCESS(200, "操作成功"),LOGIN_ERROR(201, "用户名或者密码错误"),VALIDATECODE_ERROR(202, "验证码错误"),LOGIN_AUTH(208, "用户未登录"),USER_NAME_IS_EXISTS(209, "用户名已经存在"),SYSTEM_ERROR(9999, "您的网络有问题请稍后重试"),NODE_ERROR(217, "该节点下有子节点,不可以删除"),DATA_ERROR(204, "数据异常"),ACCOUNT_STOP(216, "账号已停用"),STOCK_LESS(219, "库存不足"),;private Integer code;      // 业务状态码private String message;    // 响应消息ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}
}

二、统一异常处理

在项目中,我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,就需要统一异常处理。这里需要用到两个常用的注解:@ControllerAdvice@ExceptionHandler

2.1 全局异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {//全局异常处理@ExceptionHandler(Exception.class)@ResponseBodypublic Result error() {return Result.build(null, ResultCodeEnum.SYSTEM_ERROR);}@ExceptionHandler(GuiguException.class)@ResponseBodypublic Result error(GuiguException e) {return Result.build(null, e.getResultCodeEnum());}
}

2.2 自定义异常

自定义异常类,继承RuntimeException。

@Data
public class GuiguException extends RuntimeException {private Integer code ;          // 错误状态码private String message ;        // 错误消息private ResultCodeEnum resultCodeEnum ;     // 封装错误状态码和错误消息public GuiguException(ResultCodeEnum resultCodeEnum) {this.resultCodeEnum = resultCodeEnum ;this.code = resultCodeEnum.getCode() ;this.message = resultCodeEnum.getMessage();}public GuiguException(Integer code , String message) {this.code = code ;this.message = message ;}
}

三、登录功能实现

思路: 首先对比用户输入的验证码与redis中的验证码值是否相等,不相等则抛出GuiguException(ResultCodeEnum.VALIDATECODE_ERROR);接着获取表单中的用户名,根据用户名查询数据库,若不存在该用户,则抛出RuntimeException("用户名或者密码错误");用户存在则比较密码是否正确,密码不正确同样抛出RuntimeException("用户名或者密码错误");用户名和密码都正确时,生成令牌token,保存用户数据到redis中,最后返回token给前端。

在这里插入图片描述

代码中token是用UUID随机生成的,存放在redis中的用户数据设置的有效期是30分钟。

public LoginVo login(LoginDto loginDto) {//校验验证码String captcha = loginDto.getCaptcha();String key = loginDto.getCodeKey();String redisCode = redisTemplate.opsForValue().get("user:validate" + key);if (StrUtil.isEmpty(redisCode) || !StrUtil.equalsIgnoreCase(redisCode, captcha)) {throw new GuiguException(ResultCodeEnum.VALIDATECODE_ERROR);}redisTemplate.delete("user:validate" + key);//根据用户名查询数据库String userName = loginDto.getUserName();SysUser sysUser = sysUserMapper.selectUserInfoByUserName(userName);if (sysUser == null) {throw new GuiguException(ResultCodeEnum.LOGIN_ERROR);}//校验密码String input_password = loginDto.getPassword();String database_password = sysUser.getPassword();input_password = DigestUtils.md5DigestAsHex(input_password.getBytes());if (!input_password.equals(database_password)) {throw new GuiguException(ResultCodeEnum.LOGIN_ERROR);}//登录成功,生成用户唯一标识token,并放入redis中String token = UUID.randomUUID().toString().replaceAll("-", "");redisTemplate.opsForValue().set("user:login" + token,JSON.toJSONString(sysUser),30,TimeUnit.MINUTES);//返回loginVo对象LoginVo loginVo = new LoginVo();loginVo.setToken(token);return loginVo;}
<mapper namespace="com.atguigu.spzx.manager.mapper.SysUserMapper"><sql id="columns">id,username userName ,password,name,phone,avatar,description,status,create_time,update_time,is_deleted</sql><!--    SysUser selectUserInfoByUserName(String userName);--><select id="selectUserInfoByUserName" resultType="com.atguigu.spzx.model.entity.system.SysUser">select<include refid="columns"/>from sys_user where username = #{userName}</select>
</mapper>

四、CORS解决跨域

跨域请求: 通过一个域的JavaScript脚本和另外一个域的内容进行交互
域的信息: 协议、域名、端口号
在这里插入图片描述
同域: 当两个域的协议、域名、端口号均相同

CORS是跨域的一种解决方案,CORS给了web服务器一种权限:服务器可以选择是否允许跨域请求访问到它们的资源。

我们可以添加一个配置类,让其继承配WebMvcConfigurer 类,来置跨域请求。

@Component
public class WebMvcConfiguration implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**")      // 添加路径规则.allowCredentials(true)               // 是否允许在跨域的情况下传递Cookie.allowedOriginPatterns("*")           // 允许请求来源的域规则.allowedMethods("*").allowedHeaders("*");                // 允许所有的请求头}
}

五、图片验证码

验证码可以防止恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试。由于验证码技术具有随机性随机性较强、简单的特点,能够在一定程度上阻碍网络上恶意行为的访问,在互联网领域得到了广泛的应用。

我们将生成的4位验证码值作为value值,存入redis中,设置过期时间为5分钟;并给前端返回验证码的key以及图片验证码对应的字符串数据。

    public ValidateCodeVo generateValidateCode() {//生成验证码并放入redis中CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2);String codeValue = circleCaptcha.getCode();//4位验证码值String imageBase64 = circleCaptcha.getImageBase64();//返回图片验证码,base64编码String key = UUID.randomUUID().toString().replaceAll("-", "");redisTemplate.opsForValue().set("user:validate" + key,codeValue,5,TimeUnit.MINUTES);//返回ValidateCodeVo对象ValidateCodeVo validateCodeVo = new ValidateCodeVo();validateCodeVo.setCodeKey(key);validateCodeVo.setCodeValue("data:image/png;base64," + imageBase64);return validateCodeVo;}

六、登录校验功能实现

6.1 拦截器开发

后台管理系统中除了登录接口、获取验证码的接口在访问的时候不需要验证用户的登录状态,其余的接口在访问的时候都必须要求用户登录成功以后才可以进行访问。

在这里插入图片描述

自定义一个类,实现HandlerInterceptor接口,实现接口中的两个方法:preHandle()、afterCompletion()。首先从请求头中获取token,如果token不存在,则不放行;接着用token从redis中获取用户数据,若redis中没有用户数据,也不放行;若存在用户数据,则把用户数据息放到ThreadLocal中,并重新更新过期时间为30分钟。

@Component
public class LoginAuthInterceptor implements HandlerInterceptor {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取请求方式,如果请求方式是options(预检请求),直接放行String method = request.getMethod();if ("OPTIONS".equals(method)) {return true;}//判断用户是否登录String token = request.getHeader("token");if (StrUtil.isEmpty(token)) {responseNoLoginInfo(response);return false;}String userInfoJson = redisTemplate.opsForValue().get("user:login" + token);if (StrUtil.isEmpty(userInfoJson)) {responseNoLoginInfo(response);return false;}//把用户信息放到ThreadLocal中,并更新过期时间AuthContextUtil.set(JSON.parseObject(userInfoJson, SysUser.class));redisTemplate.expire("user:login" + token, 30, TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {AuthContextUtil.remove();}//响应208状态码给前端private void responseNoLoginInfo(HttpServletResponse response) {Result<Object> result = Result.build(null, ResultCodeEnum.LOGIN_AUTH);PrintWriter writer = null;response.setCharacterEncoding("UTF-8");response.setContentType("text/html; charset=utf-8");try {writer = response.getWriter();writer.print(JSON.toJSONString(result));} catch (IOException e) {e.printStackTrace();} finally {if (writer != null) writer.close();}}
}

注意:

1、更新Redis中数据的存活时间的主要目的就是为了保证用户在使用该系统的时候,Redis中会一直保证用户的登录状态,如果用户在30分钟之内没有使用该系统,那么此时登录超时。此时用户就需要重新进行登录。
2、将从Redis中获取到的用户存储到ThreadLocal中,这样在一次请求的中就可以在controller、service、mapper中获取用户数据

6.2 拦截器注册

为了方便路径管理,我们把需要放行的路径写在了配置文件中:

# 配置放行路径
spzx:auth:noAuthUrls:- /admin/system/index/login- /admin/system/index/generateValidateCode

实体类定义:别忘记在启动类上加入@EnableConfigurationProperties(value = {UserProperties.class})

@ConfigurationProperties(prefix = "spzx.auth")
@Data
public class UserProperties {private List<String> noAuthUrls;
}
@Component
public class WebMvcConfiguration implements WebMvcConfigurer {@Autowiredprivate LoginAuthInterceptor loginAuthInterceptor;@Autowiredprivate UserProperties userProperties;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginAuthInterceptor).excludePathPatterns(userProperties.getNoAuthUrls()).addPathPatterns("/**");}
}

七、ThreadLocal

ThreadLocal是jdk所提供的一个线程工具类,叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量,使用该工具类可以实现在同一个线程进行数据的共享。

public class AuthContextUtil {// 创建一个ThreadLocal对象private static final ThreadLocal<SysUser> threadLocal = new ThreadLocal<>() ;// 定义存储数据的静态方法public static void set(SysUser sysUser) {threadLocal.set(sysUser);}// 定义获取数据的方法public static SysUser get() {return threadLocal.get() ;}// 删除数据的方法public static void remove() {threadLocal.remove();}
}

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

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

相关文章

基于人工兔算法优化概率神经网络PNN的分类预测 - 附代码

基于人工兔算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于人工兔算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于人工兔优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

P10 C++类和结构体的区别

目录 01 前言 02 struct 与 class格式上的区别 03 struct 与 class 使用上的区别 04 常用的代码风格 01 前言 今天这期我们主要解决一个问题&#xff0c;就是 C 中的类和结构体有什么区别。 本期我们有两个术语&#xff0c;结构体 struct&#xff0c;它是 structure 的缩写…

深度学习之基于Tensorflow银行卡号码识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介银行卡号码识别的步骤TensorFlow的优势 二、功能三、系统四. 总结 一项目简介 # 深度学习基于TensorFlow的银行卡号码识别介绍 深度学习在图像识别领域取得…

【JVM】一篇通关JVM垃圾回收

目录 1. 如何判断对象可以回收1-1. 引用计数法1-2. 可达性分析算法1-3. 四种引用强引用软引用弱引用虚引用终结器引用 2. 垃圾回收算法3. 分代垃圾回收4. 垃圾回收器5. 垃圾回收调优 1. 如何判断对象可以回收 1-1. 引用计数法 引用计数法 只要一个对象被其他变量所引用&…

甲烷产生及氧化

温室气体排放被认为是加速气候变化的重要因素&#xff0c;甲烷(CH4)是仅次于二氧化碳(CO2)的重要温室气体&#xff0c;其百年温室效应潜势是CO2的28倍[1-2]。湿地中的CH4由产甲烷古菌在水体底部或沉积层严格厌氧环境下产生并释放进入水体&#xff0c;产生的CH4向上覆水运输过程…

内网横向技术

如果拿下了一台机器之后寻找域控机器 ipconfig /all 找到域名 ping 域名或者nslookup域名

【数据库】数据库物理执行计划最基本操作-表扫描机制与可选路径,基于代价的评估模型以及模型参数的含义

物理执行计划基本操作符 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏…

广州华锐互动:AR可视化展示昆虫让教学过程更直观生动

随着科技的不断发展&#xff0c;AR&#xff08;增强现实&#xff09;技术已经逐渐走进我们的生活。通过AR技术&#xff0c;我们可以将虚拟的信息叠加到现实世界中&#xff0c;让现实世界变得更加丰富多彩。在这篇文章中&#xff0c;我们将以昆虫为主题&#xff0c;探讨AR增强现…

002、ArkTS

之——开发语言 杂谈 基础编程语言ArkTS。引用来自华为开发者课堂。 ArkTS是HarmonyOS优选的主力应用开发语言。它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等相应的能力&#xff0c;让开发者以更简洁…

tp8 使用rabbitMQ(4)路由模式

路由模式 在第三节中我们使用的 交换机的 fanout 把生产者的消息广播到了所有与它绑定的队列中处理&#xff0c;但是我们能不能把特定的消息&#xff0c;发送给指定的队列&#xff0c;而不是广播给所有队列呢&#xff1f; 如图&#xff0c;交换机把 orange 类型的消息发送给了…

zookeeper 单机伪集群搭建简单记录

1、官方下载加压后&#xff0c;根目录下新建data和log目录&#xff0c;然后分别拷贝两份&#xff0c;分别放到D盘&#xff0c;E盘&#xff0c;F盘 2、data目录下面新建myid文件&#xff0c;文件内容分别为1&#xff0c;2&#xff0c;3.注意文件没有后缀&#xff0c;不能是txt文…

LED驱动控制专用电路

一、基本概述 TM1628是一种带键盘扫描接口的LED&#xff08;发光二极管显示器&#xff09;驱动控制专用IC,内部集成有MCU 数 字接口、数据锁存器、LED 驱动、键盘扫描等电路。本产品质量可靠、稳定性好、抗干扰能力强。 主要适用于家电设备(智能热水器、微波炉、洗衣机、空调…

排序算法-----基数排序

目录 前言 基数排序 算法思想 ​编辑 算法示例 代码实现 1.队列queue.h 头文件 2.队列queue.c 源文件 3.主函数&#xff08;radix_sort实现&#xff09; 算法分析 前言 今天我想把前面未更新完的排序算法补充一下&#xff0c;也就是基数排序的一种&#xff0c;这是跟…

ubuntu 使用webrtc_ros 编译linux webrtc库

ubuntu 使用webrtc_ros 编译linux webrtc库 webrtc_ros 使用WebRTC流式传输ROS图像主题 该节点提供了一个WebRTC对等方&#xff0c;可以将其配置为流ROS图像主题并接收发布到ROS图像主题的流。 该节点托管一个提供简单测试页面的Web服务器&#xff0c;并提供可用于创建和配置W…

基于Vue+SpringBoot的APK检测管理系统

项目编号&#xff1a; S 038 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S038&#xff0c;文末获取源码。} 项目编号&#xff1a;S038&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 开放平台模块2.3 软…

电子学会C/C++编程等级考试2021年09月(二级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:字符统计 给定一个由a-z这26个字符组成的字符串,统计其中哪个字符出现的次数最多。输入 输入包含一行,一个字符串,长度不超过1000。输出 输出一行,包括出现次数最多的字符和该字符出现的次数,中间以一个空格分开。如果有多…

Python入门03变量

目录 1 什么是变量2 变量声明3 变量命名规则4 变量类型5 类型转换总结 1 什么是变量 编程语言中变量就像容器一样&#xff0c;可以用来存放东西 我的变量就像杯子一样&#xff0c;可以用来盛放各种饮料。在Python中变量用来存放各种各样的数据&#xff0c;比如整数、浮点数、…

【计算机网络】HTTP 协议

文章目录 前言什么是 HTTP理解 HTTP 请求和响应格式HTTP 的请求格式1. 首行2. 请求头3. 空行4. 正文&#xff08;body&#xff09; HTTP 的响应格式1. 首行2. 响应头3. 空行4. 正文&#xff08;body&#xff09; 首行GET 和 POST 方法有什么区别针对 GET 方法和 POST 方法的区别…

ubuntu挂载硬盘方法

1.关闭服务器加上新硬盘 2.启动服务器&#xff0c;以root用户登录 3.查看硬盘信息 fdisk -l4.格式化分区 找到需要分区的目录,并记录分区的uuid&#xff0c;用于后面修改/etc/fstab永久挂载配置文件 mkfs.ext4 /dev/nvme0n1 mkfs.ext4 /dev/nvme1n1 Filesystem UUID: a1c…

【C++】构造函数和析构函数第四部分(深拷贝和浅拷贝)--- 2023.11.25

目录 什么是浅拷贝&#xff1f;浅拷贝的问题使用深拷贝解决浅拷贝问题结束语 什么是浅拷贝&#xff1f; 如果在一个类中没有人为定义拷贝函数&#xff0c;则系统会提供默认拷贝函数。那么在此默认拷贝函数中主要进行了简单的赋值操作&#xff0c;那这个简单的赋值操作我们一般…