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

news2024/12/24 21:51:56

文章目录

  • 一、统一结果实体类
  • 二、统一异常处理
  • 三、登录功能实现
  • 四、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();
    }
}

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

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

相关文章

6、独立按键控制LED亮灭

独立按键 轻触按键&#xff1a;相当于是一种电子开关&#xff0c;按下时开关接通&#xff0c;松开是开关断开 实现原理&#xff1a;是通过轻触按键内部的金属弹片受力弹动来实现接通和断开 代码&#xff1a; #include <REGX52.H>void main() {//等同于P20XFE;P2_00…

张弛声音变现课,枪战电影高能量、快速节奏

在执行枪战片的声音配音任务时&#xff0c;配音员应该致力于传递出戏剧性的紧张氛围与动作场面的激烈感。枪战场景往往是高能量、快速节奏的&#xff0c;这就要求配音不仅要与视觉动作紧密结合&#xff0c;还要通过声音来增强动作的逼真度和观众的紧迫感。以下是针对枪战电影进…

IT行业多项目管理的方法与策略:优化资源分配与提升项目成功率

多项目管理已成为项目经理们面临的常态&#xff0c;IT行业如何高效进行项目管理呢&#xff1f; 多项目管理过程中存在的问题 1、多类型项目并行&#xff0c;项目流程掺杂混乱&#xff0c;项目进度难以监控&#xff0c;反应缓慢&#xff0c;容易产生延误风险。 2、团队资源有…

LeetCode-1689. 十-二进制数的最少数目 C/C++实现 超详细思路及过程[M]

&#x1f388;归属专栏&#xff1a;深夜咖啡配算法 &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;记录一句&#xff1a;上一篇博客这里好像没改&#xff0c;那就不改了。 文章目录 LeetCode-1689. 十-二进制数的最少数目&#x1f697;题目&#x1f686;题目描述&…

2016年11月16日 Go生态洞察:Go字体的创新之旅

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

SpringBoot修改启动图标

公司基于SpringBoot框架再次定制了一个框架,那要怎么让自己封装的框架逼格高一点? 那门面就要与众不同, 哈哈哈!!!! 艺术文字网站: patorjk.com 我这里使用字体是 "怪物" 貌似框架也是使用这个字体,外国人的文化底蕴就是xxxx,推崇妖魔鬼怪

WorldWind Android上加载白模数据

这篇文章介绍下如何加载白模数据。这个白模数据的格式是shapefile格式的文件。白模数据拷贝到手机本地&#xff0c;然后读取白模数据&#xff0c;进行加载展示。 worldwind android本身是不支持加载白模数据的&#xff0c;但是可以根据现有提供的加载Polygons的方式&#xff0c…

自定义精美商品分类列表组件 侧边栏商品分类组件 category组件(适配vue3)

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。通过组件化开发&#xff0c;可以有效实现单…

【Linux】 sudo命令使用

sudo sudo是linux系统管理指令&#xff0c;是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具&#xff0c;如halt&#xff0c;reboot&#xff0c;su等等。这样不仅减少了root用户的登录 和管理时间&#xff0c;同样也提高了安全性。sudo不是对shell的一个代替…

Spring cloud - Feign

Feign的作用 Feign是Netflix公司开发的声明式web客户端组件&#xff0c;Spring对Feign做了无缝集成&#xff1a; Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has plugg…

【TC3xx芯片】TC3xx芯片的Endinit功能详解

目录 前言 正文 1.功能概述 2. WDTxCON0 的密码访问&#xff08;Password Access to WDTxCON0&#xff09; 2.1 Static Password 2.2 Automatic Password Sequencing 2.3 Time-Independent Pasword 2.4 Time Check Password 3. WDTxCON0的检查访问&#xff08;Check A…

【C++初阶】STL详解(八)List的模拟实现

本专栏内容为&#xff1a;C学习专栏&#xff0c;分为初阶和进阶两部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握C。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&…

JOSEF约瑟 JHOK-ZBZ201智能型漏电(剩余)继电器 导轨安装

JHOK-ZBZ漏电继电器&#xff08;以下简称继电器&#xff09;适用于交流电压至660V或更高的TN、TT、和IT系统&#xff0c;频率为50Hz。通过零序电流互感器检测出超过整定值的零序&#xff08;剩余&#xff09;漏电电流。该继电器与分励脱扣器或失压脱扣器的断路器、交流接触器、…

激活函数与其导数:神经网络中的关键元素

激活函数是神经网络中的重要组成部分&#xff0c;有力地推动了深度学习的发展。然而&#xff0c;仅仅了解和选择激活函数是不够的&#xff0c;我们还需要理解激活函数的导数。本文将详细介绍激活函数的概念、作用及其导数的重要性&#xff0c;并探究导数对神经网络训练的影响。…

2016年12月13日 Go生态洞察:2016年Go用户调查与企业问卷

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【UGUI】中Content Size Fitter)组件-使 UI 元素适应其内容的大小

官方文档&#xff1a;使 UI 元素适应其内容的大小 - Unity 手册 必备组件&#xff1a;Content Size Fitter 通常&#xff0c;在使用矩形变换定位 UI 元素时&#xff0c;应手动指定其位置和大小&#xff08;可选择性地包括使用父矩形变换进行拉伸的行为&#xff09;。 但是&a…

如何减少40%的Docker构建时间

随着Docker的普及&#xff0c;许多公司的产品会将组件构建为Docker镜像。但随着时间的推移&#xff0c;一些镜像变得越来越大&#xff0c;对应的CI构建也变得越来越慢。 如果能在喝完一杯咖啡的时间&#xff08;不超过5分钟&#xff09;内完成构建&#xff0c;将是一个理想状态…

使用Kibana让es集群形象起来

部署Elasticsearch集群详细步骤参考本人&#xff1a; https://blog.csdn.net/m0_59933574/article/details/134605073?spm1001.2014.3001.5502https://blog.csdn.net/m0_59933574/article/details/134605073?spm1001.2014.3001.5502 kibana部署 es集群设备 安装软件主机名…

【数据库】物理操作的一趟扫描算法机制原理,理解关系代数据与物理计划的关系,以及代价评估的应用和算法优化

一趟扫描算法 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定期更新…

【OpenGauss源码学习 —— 执行算子(Merge Join 算子)】

执行算子&#xff08;Merge Join 算子&#xff09; 连接算子Merge Join 算子ExecInitMergeJoin 函数MergeJoin 结构体 ExecMergeJoin 函数MergeJoinState 结构体 ExecEndMergeJoin 函数 总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重…