《尚品甄选》:后台系统——结合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)
    @ResponseBody
    public Result error() {
        return Result.build(null, ResultCodeEnum.SYSTEM_ERROR);
    }
    @ExceptionHandler(GuiguException.class)
    @ResponseBody
    public 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 {
    @Override
    public 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 {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Override
    public 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;
    }
    @Override
    public 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 {
    @Autowired
    private LoginAuthInterceptor loginAuthInterceptor;
    @Autowired
    private UserProperties userProperties;
    @Override
    public 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();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务知识小结

1. SOA、分布式、微服务之间有什么关系和区别&#xff1f; 1.分布式架构指将单体架构中的各个部分拆分&#xff0c;然后部署到不同的机器或进程中去&#xff0c;SOA和微服务基本上都是分布式架构的 2. SOA是一种面向服务的架构&#xff0c;系统的所有服务都注册在总线上&#…

2023亚太杯数学建模C题思路分析 - 我国新能源电动汽车的发展趋势

1 赛题 问题C 我国新能源电动汽车的发展趋势 新能源汽车是指以先进技术原理、新技术、新结构的非常规汽车燃料为动力来源( 非常规汽车燃料指汽油、柴油以外的燃料&#xff09;&#xff0c;将先进技术进行汽车动力控制和驱动相结 合的汽车。新能源汽车主要包括四种类型&#x…

外贸自建站SEO优化技巧?海洋建站怎么做?

如何做好外贸自建站SEO优化&#xff1f;谷歌独立站引流的策略&#xff1f; 拥有网站只是第一步&#xff0c;更为关键的是要确保网站在搜索引擎上获得良好的排名&#xff0c;这就需要深入了解并实施外贸自建站SEO优化技巧。海洋建站将来探讨外贸自建站的SEO优化技巧。 外贸自建…

基于C#实现并查集

一、场景 有时候我们会遇到这样的场景&#xff0c;比如:M{1,4,6,8},N{2,4,5,7}&#xff0c;我的需求就是判断{1,2}是否属于同一个集合&#xff0c;当然实现方法有很多&#xff0c;一般情况下&#xff0c;普通青年会做出 O(MN)的复杂度&#xff0c;那么有没有更轻量级的复杂度呢…

管理后台系统,springboot+redis+nginx+html+bootstrap

一个简易版的管理后台系统&#xff0c;前后端分离&#xff0c;可适用于小团队开发&#xff0c;支持二次开发。 后端主要技术springboot&#xff0c;他可以帮我们快速的搭建项目&#xff0c;并快速实现开发。 redis做缓存&#xff0c;保存登录状态和一些高频率查询的基础数据。…

【Unity细节】Unity中为什么用字符串加载对象,检查多便都加载不出来—(命名细节)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

小红书达人类型特点有哪些,创作形式总结!

小红书自带的社交电商属性&#xff0c;吸引了众多优秀的内容创作者和品牌达人。他们以不同的风格和主题&#xff0c;赢得了粉丝们的喜爱和关注。今天为大家分享下小红书达人类型特点有哪些&#xff0c;创作形式总结&#xff01; 1. 内容创作风格 我们从内容上来区分小红书达人类…

【论文解读】在上下文中学习创建任务向量

一、简要介绍 大型语言模型&#xff08;LLMs&#xff09;中的上下文学习&#xff08;ICL&#xff09;已经成为一种强大的新的学习范式。然而&#xff0c;其潜在的机制仍未被很好地了解。特别是&#xff0c;将其映射到“标准”机器学习框架是具有挑战性的&#xff0c;在该框架中…

视频录制工具有哪些?收藏起来,需要的时候用起来

视频录制工具顾名思义&#xff1a;用于捕获视频片段的软件。使用视频录制工具&#xff0c;你可以创建属于自己的视频内容。市面上的录屏工具五花八门&#xff0c;有哪些才是适合自己的呢&#xff1f; 虽然有许多视频录制工具可供选择&#xff0c;甚至有很多是免费的&#xff0…

智安网络|如何最大限度地提高企业网络安全水平

在当今数字化时代&#xff0c;企业面临着日益复杂和智能化的网络威胁。为了保护企业的机密信息和客户数据&#xff0c;漏洞扫描成为了一个至关重要的安全措施。然而&#xff0c;对于企业来说&#xff0c;他们最关心的是什么问题呢&#xff1f; 一、漏洞的发现和修复 在网络安全…

SOAP 协议和 HTTP 协议:深入解读与对比

SOAP 和 HTTP 协议 SOAP 协议 SOAP&#xff08; Simple Object Access Protocol&#xff09;是一种用于在节点之间交换结构化数据的网络协议。它使用XML格式来传输消息。它在 HTML 和 SMTP 等应用层协议的基础上进行标记和传输。SOAP 允许进程在整个平台、语言和操作系统中进…

【ChatGLM3-6B】Docker下部署及微调

【ChatGLM2-6B】小白入门及Docker下部署 注意&#xff1a;Docker基于镜像中网盘上上传的有已经做好的镜像&#xff0c;想要便捷使用的可以直接从Docker基于镜像安装看Docker从0安装前提下载启动访问 Docker基于镜像安装容器打包操作&#xff08;生成镜像时使用的命令&#xff0…

火爆火爆!影响超250万读者,Python入门圣经全新升级!

人生苦短&#xff0c;快学Python&#xff01; 什么&#xff1f;你没用过&#xff0c;也没开始学习&#xff0c;甚至没有认真了解过这门语言&#xff1f;那你一定这一秒就开始发力——下面让我们先简单看看 Python 有多火。权威编程语言排行榜 TIOBE&#xff0c;2022 和 2023 都…

案例015:Java+SSM+uniapp基于微信小程序的校园防疫系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

化学气相沉积(CVD)中的TEOS

在半导体制程中&#xff0c;薄膜的沉积是核心的步骤之一&#xff0c;有接触过CVD的小伙伴应该或多或少听过TEOS这种物质&#xff0c;TEOS作为一种重要的沉积源&#xff0c;尤其在低温氧化硅的生成过程中&#xff0c;发挥了无可替代的角色。今天我们就来聊聊这种物质。 什么是TE…

勒索病毒:数字化时代的“黑帮敲诈”,如何防范避免成为下一个受害者?

近日&#xff0c;加拿大政府披露了一起重大黑客攻击事件。据官方消息&#xff0c;两家政府承包商BGRS和SIRVA Canada沦为黑客攻击目标&#xff0c;导致数量不明的政府雇员敏感信息泄露。此次泄露的信息不仅涉及普通政府雇员&#xff0c;还牵扯到加拿大皇家骑警&#xff08;RCMP…

【论文阅读笔记】Smil: Multimodal learning with severely missing modality

Ma M, Ren J, Zhao L, et al. Smil: Multimodal learning with severely missing modality[C]//Proceedings of the AAAI Conference on Artificial Intelligence. 2021, 35(3): 2302-2310.[开源] 本文的核心思想是探讨和解决多模态学习中的一个重要问题&#xff1a;在训练和测…

Mobaxterm 使用lrzsz传输文件(rz/sz)

Mobaxterm 使用lrzsz传输文件报错 1. 现象 最近从xshell切换到Mobaxterm其他一切正常,就是使用rz传输文件时会出现错误,比较苦恼. 会出现以下错误 [rootcentos7 rpmbuild]# rz ▒CCCCCCCCCCC23be50ive.**B0100000023be502. 解决方法 去官网(https://mobaxterm.mobatek.net…

136. 只出现一次的数字

136. 只出现一次的数字 题目&#xff1a; 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空…

【超详细】手搓一个微信日记本

&#x1f380; 文章作者&#xff1a;二土电子 &#x1f338; 关注公众号获取更多资料&#xff01; &#x1f438; 期待大家一起学习交流&#xff01; 这里对之前的微信记事本小程序进行了重新编写&#xff0c;增加了更加详细的步骤描述&#xff0c;将全部图片都改成了本地图…