Redis学习Day3——项目工程开发

news2024/11/13 10:21:11

扩展阅读推荐:

黑马程序员Redis入门到实战教程_哔哩哔哩_bilibili

一、项目介绍及其初始化

        学习Redis的过程,我们还将遇到各种实际问题,例如缓存击穿、雪崩、热Key等问题,只有在实际的项目实践中解决这些问题,才能更好的掌握和理解Redis的企业开发思维。

        以下是本次【黑马点评】项目的主要内容:

本项目的功能概述
本项目的搭建结构
本项目的搭建结构

1.1 数据库连接配置

在yml配置文件中,配置好自己的数据库连接信息

数据库表结构

1.2 工程启动演示

想要成功启动该项目,需要以下步骤:

1. 打开VM虚拟机,激活Linux系统中事先配置好的Redis数据库

2. 启动nignx服务器(注意nignx服务器必须放在无中文的目录下)

3. 启动后端程序(观察端口,访问初始工程)

4. 访问请求地址,验证工程启动正确http://localhost:8081/shop-type/list

前端网页端口:8080 
后端数据端口:8081

二、短信登录功能实现

2.1 基于传统Session实现的短信登录及其校验

2.1.1 基于Session登录校验的流程设计

2.1.2 实现短信验证码发送功能

请求接口/user/code
请求类型post
请求参数phone
返回值
    /**
     * 发送手机验证码
     */
    @PostMapping("/code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        log.info("发送验证码, 手机号:{}", phone);
        return userService.sendCode(phone, session);
    }

    /**
     * 发送验证码
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1. 校验手机号码
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号码格式错误!");
        }
        // 2. 生成验证码
        String code = RandomUtil.randomNumbers(6);
        // 3. 将验证码保存到Session中
        session.setAttribute("code", code);
        //TODO 4. 调用阿里云 将短信信息发送到指定手机
        log.info("发送短信验证码成功,验证码:{}", code);
        return Result.ok();
    }

2.1.3 实现登录、注册功能

请求接口/user/login
请求类型post
请求参数LoginForm---> phone,code,[password]
返回值
    /**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        log.info("用户登录, 参数:{}", loginForm);
        return userService.login(loginForm, session);
    }


/**
     * 登录功能
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1. 校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号码格式错误!");
        }
        // 2. 校验验证码
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();
        if(cacheCode==null || !cacheCode.toString().equals(code)){
            return Result.fail("验证码错误!");
        }
        // 3. 根据手机号查询用户 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        //    if 0 :创建新用户,保存数据库,将用户信息存储到Session
        //
        if(user == null){
            user = createUserWithPhone(phone);
        }
        //else: 登录成功,将用户信息存储到Session
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
        return Result.ok();
    }


/**
     * 根据手机号创建用户
     * @param phone
     * @return
     */
    private User createUserWithPhone(String phone) {
        //创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        //保存用户
        save(user);
        // 返回
        return user;
    }

2.1.4 实现登录状态校验拦截器

由于日后项目功能会越来越多,需要登录才能进行访问的界面也会越来越多,我们必须想办法将登录状态校验抽离出来形成一个前置校验的条件,再放行到后续逻辑。

1. 封装TreadLocal工具类

将用户信息保存到 TreadLocal中 并封装TreadLocal工具类用于 保存用户、获取用户、移除用户

在 urils / UserHolder 

/**
 * TreadLocal工具类
 */
public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
    // 保存用户
    public static void saveUser(UserDTO user){
        tl.set(user);
    }
    // 获取ThreadLocal中的用户
    public static UserDTO getUser(){
        return tl.get();
    }
    // 清空ThreadLocal
    public static void removeUser(){
        tl.remove();
    }
}
2. 创建登录拦截器

在 urils / LoginInterceptor 

public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 前置拦截器
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取session
        HttpSession session = request.getSession();
        // 2. 获取session中的用户
        Object user = session.getAttribute("user");
        // 3. 判断用户是否存在
        if(user == null){
            response.setStatus(401);
            return false;
        }
        // 4. 如果存在,用户信息保存到 ThreadLocal 并放行
        UserHolder.saveUser((UserDTO) user);
        return true;
    }

    /**
     * 后置拦截器(移除用户)
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}
3. 添加配置,生效拦截器,并配置放行路径

在 config/ MvcConfig

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    /**
     * 添加拦截器
     * @param registry
     */
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/host",
                        "/shop/**",
                        "/shop-type/**",
                        "/voucher/**"
                );
    }
}

2.1.5 实现获取用户请求

前端点击我的,发送请求到后端,获取当前登录状态,方能进入个人中心

    /**
     * 获取当前登录的用户
     * @return
     */
    @GetMapping("/me")
    public Result me(){
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }

2.1.6 (附加)用户信息脱敏处理

为防止出现以下这种情况(将用户隐私信息暴露过多),我们采用UserDTO对象对用户信息脱敏处理:

@Data
public class UserDTO {
    private Long id;
    private String nickName;
    private String icon;
}

并借助拷贝工具 进行对象拷贝

session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

2.2 传统Session在集群环境下的弊端

Session共享问题

多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。

解决策略

1. 让Session可以共享

Tomcat提供了Session拷贝功能,但是这会增加服务器的额外内存开销,并且带来数据一致性问题

2. 【推荐】使用Redis进行替代

数据共享、内存存储(快)、key-value结构

2.3 基于Redis实现短信登录功能

2.3.1 基于Redis实现短信登录流程设计

发送短信验证码逻辑
校验登录状态逻辑

对于验证码,使用 手机号码作为KEY,确保了正确的手机对应着正确的短信验证码。

对于用户信息唯一标识使用 UUID生成的Token作为 KEY,而不使用手机号码,从而提高了用户数据安全性。

2.3.2 修改发送短信验证码功能

只需要在Session的基础上,将第三步保存到Redis中

格式:

keyvalueTTL
login:code:[手机号码][验证码]120S
//        // 3. 将验证码保存到Session中
//        session.setAttribute("code", code);

        // 3. 将验证码保存到Redis中
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

2.3.3 修改登录、注册功能

1. 手机号校验

2. 从Redis中取出验证码进行校验

3.查询用户信息

4. 将用户信息存储到Redis ---> 需要以Hash结构进行存储 ----> 需要将user对象转成 Map对象

5. 将token返回给客户端 ,充当Session的标识作用

/**
     * 登录功能
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1. 校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号码格式错误!");
        }
        // 2. 校验验证码 REDIS
//        Object cacheCode = session.getAttribute("code");
        // 2.1 从Redis中获取验证码
        String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        // 2.2 校验验证码
        String code = loginForm.getCode();
        if(redisCode==null || !redisCode.equals(code)){
            return Result.fail("验证码错误!");
        }
        // 3. 根据手机号查询用户 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        //    if 0 :创建新用户,保存数据库,将用户信息存储到Session
        if(user == null){
            user = createUserWithPhone(phone);
        }
//        //else: 登录成功,将用户信息存储到Session
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

        // 4. 将用户信息存储到Redis中
         // 1. 随机生成token,作为登录令牌 ---> UUID导入工具包中的方法,不要导入java自带的
        String token = UUID.randomUUID().toString(true);
         // 2. 以hash结构进行存储
        UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
        //TODO 这里报错了,因为UserDTO中有个id属性,不是字符串,在Redis序列化下报错
//        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName,fieldValue)-> fieldValue.toString()));
        // 3. 存储到Redis中
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);

        // 给token设置有效期
        // 超过30分钟不访问任何界面就会剔除,所以还需要设置在访问过程中不断更新token的有效期
        // 实现方式: 在登录拦截器中进行处理
        stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);

        // 5. 返回token到客户端,客户端保存到浏览器中
        return Result.ok(token);
    }

2.3.4 修改登录校验拦截器逻辑

首先,由于需要在自定义的拦截器中使用StringRedisTemplate对象

2.4 TODO 基于阿里云完善验证码功能

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

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

相关文章

DPDI基础版安装部署说明

DispatchPDI下载 DPDI online部署包下载地址&#xff1a; Windows: http://files.pizzalord.site/api/public/dl/7Tnq6ScE/release/dpdi-community-win.zip Linux: http://files.pizzalord.site/api/public/dl/otCt9WuI/release/dpdi-community-linux.zip DPDI部署 DPDI应…

python构建深度学习模型开发数据采集利器,为模型提供充足图像数据

经常需要接触到各种各样的图像数据&#xff0c;为模型开发准备素材&#xff0c;在实际的项目中&#xff0c;一部分数据来源于真实的项目场景&#xff0c;但是这部分数据大都比较少&#xff0c;且获取的难度比较大&#xff0c;往往都是项目到了实施阶段的时候才有机会拿到数据&a…

基于SpringBoot的医院挂号预约管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的医院挂号预约管理…

【有啥问啥】探索扫地机器人中的 SLAM 算法:原理、实现与未来展望

探索扫地机器人中的 SLAM 算法&#xff1a;原理、实现与未来展望 随着智能家居的普及&#xff0c;扫地机器人逐渐成为日常生活中的常见家电。其自主导航能力使得它能够在复杂的家庭环境中高效完成清洁任务&#xff0c;而这背后的核心技术之一就是 SLAM&#xff08;Simultaneou…

CCS12.2 以及以上版本如何使用C2000ware 4.03版本,发现直接导入工程不能正确识别地址变量?

C2000ware 5.02 地址变量为&#xff1a;COM_TI_C2000WARE_INSTALL_DIR&#xff0c;CCS12.2以上版本能够直接匹配识别&#xff01; 但是C2000ware4.03版本地址变量为&#xff1a;COM_TI_C2000WARE_SOFTWARE_PACKAGE_INSTALL_DIR&#xff0c;这个不能直接识别到头文件的地址&…

appium server gui详细按照步骤

1.安装appium server desktop Appium安装提供两种方式:桌面版和命令行版。其中桌面版又分为 Appium GuI 和 Appium Desktop 。作为初学者&#xff0c;用桌面版&#xff0c;对初学者比较友好。 官网下载地址&#xff1a;Releases appium/appium-desktop GitHubTags appium/…

基于FPGA与RK3588的多通道低延时3G-SDI视频信号

目录 简介 主要用途&#xff1a; 项目简介&#xff1a; 详细过程&#xff1a; BT1120视频时序转CEA861 视频像素编码格式转换 低延时处理 MIPI接口处理 视频处理模块 ​​​​​​​ 简介 主要解决 RK3588支持多种视频格式输入&#xff0c;但是没有支持多路SDI接口的…

【JUC】14-LongAddr源码分析

1. LongAddr底层实现过程 2. Striped64中变量或方法的定义 base&#xff1a;类似于AtomicLong中全局的value值。在没有竞争情况下数据直接累加到base上&#xff0c;或者cells扩容时&#xff0c;也需要将数据写入到base上。collide&#xff1a;表示扩容意向&#xff0c;false一…

反转链表 II

题目 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a;…

Java中的ArrayList类

继承实现关系 Arraylist就是一个可以动态扩容的容器&#xff0c;属于集合类的一种&#xff0c;要追根溯源的话它是间接实现了Collection接口&#xff0c;下面我画一下它的结构图 类定义 ArrayList类继承自抽象类AbstractList&#xff0c;同时实现了List和Collection接口&…

服务器数据恢复—通过拼接数据库碎片的方式恢复SQL Server数据库数据

服务器数据恢复环境&#xff1a; 一台服务器中有一组由4块STAT硬盘通过RAID卡组建的RAID10阵列&#xff0c;上层是XenServer虚拟化平台&#xff0c;虚拟机安装Windows Server操作系统&#xff0c;作为Web服务器使用。 服务器故障&#xff1a; 因机房异常断电导致服务器中一台V…

元学习之应用案例

现在在做元学习的时候&#xff0c;我们最常拿来测 试元学习技术的任务叫做少样本图像分类&#xff0c;简单来讲就是每一个任务都只有几张图片&#xff0c;每一 个类别只有几张图片。比如我们使用图1的案例为例说明。现在分类的任务是分为三个 类别&#xff0c;每个类别都只有两…

贪心-用最少的箭射球

一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭&#xff0c;若有一个气球的直径的开始和结束坐标为 xstart&#xff0c;xend&#xff0c; 且满足 xstart ≤ x ≤ xend&#xff0c;则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后…

MySQL从C盘迁移到D盘

文章目录 前言一、停止MySQL服务打开服务&#xff08;方式一&#xff09;打开服务&#xff08;方式二&#xff09;停止MySQL服务 二、找到C盘中的文件文件夹1文件夹2文件夹3 三、修改文件内容1.对应文件夹12.对应文件夹3 四、 修改注册表中文件路径1.打开注册表2. 修改注册表中…

微积分-积分应用5.5(函数的平均值)

很容易计算有限多个数字 y 1 , y 2 , … , y n y_1, y_2, \dots, y_n y1​,y2​,…,yn​ 的平均值&#xff1a; y ave y 1 y 2 ⋯ y n n y_{\text{ave}} \frac{y_1 y_2 \cdots y_n}{n} yave​ny1​y2​⋯yn​​ 但是&#xff0c;如果可以进行无限多次的温度读取&…

Java 入门指南:Java 并发编程 —— 同步工具类 CountDownLatch(倒计时门闩)

文章目录 同步工具类CountDownLatch常用方法使用步骤适用场景使用示例 同步工具类 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 提供的用于并发编程的工具类库&#xff0c;其中包含了一些通信工具类&#xff0c;用于在多个线程之间进行协调和通信&#xff0c;特别…

【kafka】kafka如何保证数据的可靠性,kafka如何保证数据不丢失

1. Kafka架构&#xff1a; Producer - Broker - Consumer 回到问题上来&#xff0c;Kafka如何保证数据不丢失&#xff0c;我们先看看Kafka如何保证Producer端数据不丢失&#xff1a; 通过ack机制 最小副本数设置 生产者重试机制 2. Kafka Producer消息发送ACK机制&#xff1…

量化交易backtrader实践(一)_数据获取篇(2)_tushare与akshare

上一节回顾 在上一节中&#xff0c;从股票的基本功能和主要数据进行小结&#xff0c;明确了进行backtrader回测所需要的数据&#xff0c;并且学习了backtrader的数据来源以及PandasData的格式要求&#xff0c;已经做到假设拿到.txt或.csv文件后&#xff0c;能把里面的股票基本…

【代码随想录】字符串

本博文为《代码随想录》学习笔记&#xff0c;原文链接&#xff1a;代码随想录 344.反转字符串 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的…

Qt使用绿色pdf阅读器打开文件

1.下载SumatraPDF 2.设置 3.代码 void MainWindow::on_pushButton_clicked() {QProcess *process new QProcess();QString filePath "C:\\Users\\jude\\Desktop\\su\\11.pdf";QString sumatraPath "C:\\Users\\jude\\Desktop\\su\\SumatraPDF-3.5.2-64.exe&q…