《尚品甄选》:后台系统——结合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.hqwc.cn/news/216016.html

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

相关文章

【信息隐藏】信息隐藏基础

00 学习资源 0.1 推荐书籍 1.多媒体安全基础导论 复旦大学出版社 蓝皮&#xff1b; 2.隐写学原理与技术&#xff08;赵险峰&#xff09;科学出版社 蓝皮 0.2 视频课程 南开大学-信息隐藏技术&#xff08;没看&#xff09; 0.3 代码资源 GitHub一位phd&#xff1a;https:/…

JDBC编程方法及细节

JDBC&#xff08;Java Database Connectivity&#xff09;是Java编程语言用于连接和操作数据库的API&#xff08;Application Programming Interface&#xff09;。它为开发人员提供了一组Java类和接口&#xff0c;用于与各种关系型数据库进行通信。使用JDBC&#xff0c;开发人…

点大商城V2.5.3分包小程序端+小程序上传提示限制分包制作教程

这几天很多播播资源会员反馈点大商城V2.5.3小程序端上传时提示大小超限&#xff0c;官方默认单个包都不能超过2M&#xff0c;总分包不能超20M。如下图提示超了93KB&#xff0c;如果出现超的不多情况下可采用手动删除一些images目录下不使用的图片&#xff0c;只要删除超过100KB…

git提交报错error: failed to push some refs to ‘git url‘

1.产生错误原因 想把本地仓库提交到远程仓库&#xff0c;报错信息如下 git提交报错信息 error: src refspec master does not match any error: failed to push some refs to git url 错误原因&#xff1a; 我们在创建仓库的时候&#xff0c;都会勾选“使用Reamdme文件初始化…

发送一个网络数据包的过程解析

在 ip_queue_xmit 中&#xff0c;也即 IP 层的发送函数里面&#xff0c;有三部分逻辑。第一部分&#xff0c;选取路由&#xff0c;也即我要发送这个包应该从哪个网卡出去。 这件事情主要由 ip_route_output_ports 函数完成。接下来的调用链为&#xff1a;ip_route_output_port…

apipost接口200状态码,浏览器控制台500状态码

后端 url 登录login方法 login(){this.$refs.loginForm.validate(async valid > {if (!valid) return// 由于data属性是一个json对象&#xff0c;需要进行解构赋值{data:result}&#xff0c;进行状态码判断const {data: result} await this.$http.post(/api/doLogin,this.…

《使用Python将Excel数据批量写入MongoDB数据库》

在数据分析及处理过程中&#xff0c;我们经常需要将数据写入数据库。而MongoDB作为一种NoSQL数据库&#xff0c;其具有强大的可扩展性、高性能以及支持复杂查询等特性&#xff0c;广泛用于大规模数据存储和分析。在这篇文章中&#xff0c;我们将使用Python编写一个将Excel数据批…

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?

大家好&#xff0c;我是前端西瓜哥。这次简单看看 SVGEdit 的架构。 SVGEdit 的版本为 7.2.0。 SVGEdit 一款非常老牌的 SVG 图形编辑器&#xff0c;用于编辑处理 SVG&#xff0c;start 数目前是 5.8k。 它的优点在于经过多年的开发&#xff0c;完成度高&#xff0c;较为成熟&a…

javascript判断是否是json格式

文章目录 一、问题二、解决三、总结3.1、定义 一、问题 工作中有用到JSON.parse这个来解析JSON字符串&#xff0c;这个时候突然有一次遇到JSON字符串是长串数字或数字字符串&#xff0c;主要是自己也没兼容好&#xff0c;就导致了一长串数字JSON.parse之后变成了e24等数字。主…

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

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

吴恩达《机器学习》10-1-10-3:决定下一步做什么、评估一个假设、模型选择和交叉验证集

一、决定下一步做什么 在机器学习的学习过程中&#xff0c;我们已经接触了许多不同的学习算法&#xff0c;逐渐深入了解了先进的机器学习技术。然而&#xff0c;即使在了解了这些算法的情况下&#xff0c;仍然存在一些差距&#xff0c;有些人能够高效而有力地运用这些算法&…

Axios 拦截器 请求拦截器 响应拦截器

请求拦截器 相当于一个关卡&#xff0c;如果满足条件就放行请求&#xff0c;不满足就拦截 响应拦截器 在处理结果之前&#xff0c;先对结果进行预处理&#xff0c;比如&#xff1a;对数据进行一下格式化的处理 全局请求拦截器 axios.interceptors.request.use(config > { /…