返回顶部
分享到

基于Redis实现登录功能思路介绍(手机号+验证码)

Redis 来源:互联网 作者:佚名 发布时间:2026-01-08 21:01:34 人浏览
摘要

本文使用的是手机号+验证码的登录方式,其中验证码是通过在控制台输出,并没有真的发送到手机上(太麻烦,主要目的还是学习使用Redis) 重点是看思路,而不是具体的代码实现 UserServi

本文使用的是 手机号+验证码 的登录方式,其中验证码是通过在控制台输出,并没有真的发送到手机上(太麻烦,主要目的还是学习使用Redis)

重点是看思路,而不是具体的代码实现

UserServiceImpl实现类

整体结构

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Slf4j

@Service

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired

    private StringRedisTemplate stringRedisTemplate;

    @Override

    public Result sendCode(String phone, HttpSession session) {

        //...

    }

    @Override

    public Result login(LoginFormDTO loginForm, HttpSession session) {

        //...

    }

    private User createUserWithPhone(String phone) {

        //...

    }

}

sendCode方法

这个是发送验证码的方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public Result sendCode(String phone, HttpSession session) {

    // 1. 校验手机号

    if (RegexUtils.isPhoneInvalid(phone)) {

        // 2. 如果不符合,返回错误信息

        return Result.fail("手机号格式错误!");

    }

    // 3. 如果符合,生成验证码

    String code = RandomUtil.randomNumbers(6);

    // 4. 保存验证码到redis

    stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY +phone,code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);

    // 5. 发送验证码

    log.debug("发送短信验证码成功,验证码:{}", code);

    // 6. 返回结果

    return Result.ok();

}

注:这里的RedisConstants是一个用来存放各种常量的类

1

2

3

4

5

6

public class RedisConstants {

    public static final String LOGIN_CODE_KEY = "login:code:";

    public static final Long LOGIN_CODE_TTL = 2L;

    public static final String LOGIN_USER_KEY = "login:token:";

    public static final Long LOGIN_USER_TTL = 30L;

}

login方法

这里使用了MybatisPlus来操作数据库(User user = query().eq("phone", phone).one();),但是这个不是重点

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

public Result login(LoginFormDTO loginForm, HttpSession session) {

    // 1. 校验手机号

    String phone = loginForm.getPhone();

    if (RegexUtils.isPhoneInvalid(phone)) {

        return Result.fail("手机号格式错误!");

    }

    // 2. 从redis获取验证码并校验

    String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY +phone);

    String code = loginForm.getCode();

    if (cacheCode == null || !cacheCode.equals(code)) {

        // 3. 不一致,报错

        return Result.fail("验证码错误!");

    }

    // 4. 一致,根据手机号查询用户

    User user = query().eq("phone", phone).one();

    // 5. 判断用户是否存在

    if (user == null) {

        // 6. 不存在,创建新用户并保存

        user = createUserWithPhone(phone);

    }

    // 7. 保存用户信息到redis

    String token= UUID.randomUUID().toString(true);

    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),

            CopyOptions.create()

                    .setIgnoreNullValue(true)

                    .setFieldValueEditor((fieldName, fieldValue)->fieldValue.toString()));

    stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, userMap);

    stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

    return Result.ok(token);

}

createUserWithPhone方法

在login方法中调用了该方法

这里也使用了MybatisPlus来操作数据库(save(user);)

1

2

3

4

5

6

7

8

9

private User createUserWithPhone(String phone) {

    // 1. 创建用户

    User user = new User();

    user.setPhone(phone);

    user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));

    // 2. 保存用户

    save(user);

    return user;

}

拦截器

整体框架

其实就是实现了HandlerInterceptor的两个方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@Slf4j

@Component

public class LoginInterceptor implements HandlerInterceptor {

    @Autowired

    private StringRedisTemplate stringRedisTemplate;

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //...

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        // 移除用户

        UserHolder.removeUser();

    }

}

 UserHolder是ThreadLocal 持有类

1

2

3

4

5

6

7

8

9

10

11

12

public class UserHolder {

    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){

        tl.set(user);

    }

    public static UserDTO getUser(){

        return tl.get();

    }

    public static void removeUser(){

        tl.remove();

    }

}

preHandle方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // 1.获取请求头中的token

    String token = request.getHeader("authorization");

    if (StrUtil.isBlank(token)) {

        // 不存在,拦截

        response.setStatus(401);

        return false;

    }

    // 2.基于token获取redis中的用户

    String key = RedisConstants.LOGIN_USER_KEY + token;

    Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);

    // 3.判断用户是否存在

    if (userMap.isEmpty()) {

        // 4.不存在,拦截

        response.setStatus(401);

        return false;

    }

    // 5.将查询到的Hash数据转换为UserDTO对象

    UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

    // 6.存在,保存用户信息到ThreadLocal

    UserHolder.saveUser(userDTO);

    // 7.刷新token有效期

    stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

    // 8.放行

    return true;

}

注:authorization 是前端定义的用来传递token的key

配置类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Configuration

public class MvcConfig implements WebMvcConfigurer {

    @Autowired

    private LoginInterceptor loginInterceptor;

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(loginInterceptor)

                .addPathPatterns("/**")

                .excludePathPatterns(

                        "/user/code",

                        "/user/login",

                        "/blog/hot",

                        "/shop/**",

                        "/shop-type/**",

                        "/upload/**",

                        "/voucher/**"

                );

    }

}

整体思路

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

flowchart TD

subgraph A[发送验证码流程]

    A1["前端请求 发送验证码"] --> A2["校验手机号格式"]

    A2 -- 不合法 --> A3["返回错误  手机号格式错误"]

    A2 -- 合法 --> A4["生成6位验证码"]

    A4 --> A5["保存验证码到Redis"]

    A5 --> A6["返回成功"]

end

subgraph B[登录流程]

    B1["前端请求 登录"] --> B2["校验手机号格式"]

    B2 -- 不合法 --> B3["返回错误"]

    B2 -- 合法 --> B4["从Redis获取验证码"]

    B4 --> B5{"验证码是否正确"}

    B5 -- 否 --> B6["返回验证码错误"]

    B5 -- 是 --> B7["根据手机号查询用户"]

    B7 --> B8{"用户是否存在"}

    B8 -- 否 --> B9["创建新用户"]

    B8 -- 是 --> B10["使用已有用户"]

    B9 --> B11["生成Token"]

    B10 --> B11

    B11 --> B12["用户信息写入Redis"]

    B12 --> B13["返回Token"]

end

subgraph C[请求拦截流程]

    C1["请求到达拦截器"] --> C2["从请求头获取Token"]

    C2 --> C3{"Token是否存在"}

    C3 -- 否 --> C4["返回401"]

    C3 -- 是 --> C5["从Redis获取用户信息"]

    C5 --> C6{"用户是否存在"}

    C6 -- 否 --> C4

    C6 -- 是 --> C7["保存用户到ThreadLocal"]

    C7 --> C8["刷新Token有效期"]

    C8 --> C9["放行请求"]

end

subgraph D[请求结束]

    D1["请求完成"] --> D2["清理ThreadLocal"]

end

B13 --> C1

C9 --> D1

复制到未命名绘图 - draw.io中用mermaid格式文件创建流程图

优化

目前之后访问被拦截的页面才会刷新有效期,所以这里我们需要优化一下

方式是采用拦截器链,即再加一个拦截器来拦截全部页面,以此来更新有效期

RefreshTokenInterceptor

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

@Slf4j

@Component

public class RefreshTokenInterceptor implements HandlerInterceptor {

    @Autowired

    private StringRedisTemplate stringRedisTemplate;

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 1.获取请求头中的token

        String token = request.getHeader("authorization");

        if (StrUtil.isBlank(token)) {

            return true;

        }

        // 2.基于token获取redis中的用户

        String key = RedisConstants.LOGIN_USER_KEY + token;

        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);

        // 3.判断用户是否存在

        if (userMap.isEmpty()) {

            return true;

        }

        // 5.将查询到的Hash数据转换为UserDTO对象

        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 6.存在,保存用户信息到ThreadLocal

        UserHolder.saveUser(userDTO);

        // 7.刷新token有效期

        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        // 8.放行

        return true;

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        // 移除用户

        UserHolder.removeUser();

    }

}

LoginInterceptor

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Slf4j

@Component

public class LoginInterceptor implements HandlerInterceptor {

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 判断是否需要拦截(ThreadLocal中是否有用户)

        if (UserHolder.getUser() == null) {

            response.setStatus(401);

            return false;

        }

        // 有用户,则放行

        return true;

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        // 移除用户

        UserHolder.removeUser();

    }

}

配置类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

@Configuration

public class MvcConfig implements WebMvcConfigurer {

    @Autowired

    private LoginInterceptor loginInterceptor;

    @Autowired

    private RefreshTokenInterceptor refreshTokenInterceptor;

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        // 登录拦截器

        registry.addInterceptor(loginInterceptor)

                .addPathPatterns("/**")

                .excludePathPatterns(

                        "/user/code",

                        "/user/login",

                        "/blog/hot",

                        "/shop/**",

                        "/shop-type/**",

                        "/upload/**",

                        "/voucher/**"

                ).order(1);

        // 刷新token拦截器

        registry.addInterceptor(refreshTokenInterceptor)

                .addPathPatterns("/**").order(0);

    }

}

注:order方法是用来设置哪一个拦截器在前,哪一个在后;规则:数字小的在前,数字大的在后


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 基于Redis实现登录功能思路介绍(手机号+验证码
    本文使用的是手机号+验证码的登录方式,其中验证码是通过在控制台输出,并没有真的发送到手机上(太麻烦,主要目的还是学习使用R
  • redis中session会话共享的三种方案
    在分布式系统架构中,用户请求可能被负载均衡器分发到不同的服务器节点。如果用户的第一次请求落在服务器A并创建了Session,而第二次请
  • shell脚本批量导出redis key-value方式
    1 背景 需求:工作中需要导出线上redis数据,但需避免使用keys命令全量扫描,导致瞬间响应卡顿,从而引发超时等问题 方法:最安全的方式
  • redis通用配置类的使用

    redis通用配置类的使用
    redis通用配置类 作用 处理Springboot使用 RedisTemplate过程中的编码问题 现象如下,看数据的时候不方便 所以添加一下的配置类之后,就可以了
  • linux部署redis集群遇到的问题及解决
    版本信息: redis:5.0.8 linux服务器:CentOS 7 不同版本问题处理方式可能有所不同 1、在java程序中,连接不上redisCluster 报错信息: no reachable
  • Redis中对大Key进行处理方式

    Redis中对大Key进行处理方式
    什么是大key 很多铁子可能会认为大key,是这个key的值很大其实不是,而是key的value值很大一般对于下面这些我们可以称为大key. String 类型值
  • 一文浅析如何在Redis中实现缓存功能
    Redis 是一种高性能的键值存储系统,广泛用于实现缓存功能。它通过将数据存储在内存中,能够快速读写数据,从而显著提高应用程序的性
  • Redis Cluster模式配置
    分片 一、分片的本质与核心价值 问题根源 单机 Redis 存在内存容量和吞吐量瓶颈,分片通过将数据分散到多个节点解决此问题。 核心价值
  • Redis中的Lettuce使用介绍
    Lettuce 是一个高级的、线程安全的 Redis 客户端,用于与 Redis 数据库交互。它提供了许多方法来配置连接池的参数,例如最大连接数、最小空
  • redis过期key的删除策略
    在使用redis的过程中,不免会产生过期的key,而这些key过期后并不会实时地马上被删除,当这些key数量累积越来越多,就会占用很多内存,因
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计