搜索
首页数据库RedisRedis实现短信登录的企业实例分析

一、导入黑马点评项目

黑马点评项目主要包括以下功能:

Redis实现短信登录的企业实例分析

1. 导入SQL

需要项目资料的私信我

其中的表有:

  • tb_user:用户表

  • tb_user_info:用户详情表

  • tb_shop:商户信息表

  • tb_shop_type:商户类型表

  • tb_blog:用户日记表(达人探店日记)

  • tb_follow:用户关注表

  • tb_voucher:优惠券表

  • tb_voucher_order:优惠券的订单表

注意:Mysql的版本采用5.7及以上版本

Redis实现短信登录的企业实例分析

2. 前后端分离

Redis实现短信登录的企业实例分析

3. 导入后端项目

3.1 将后端项目导入到 Idea 中

Redis实现短信登录的企业实例分析

3.2 注意:修改application.yaml文件中的mysql、redis地址信息 将mysql、redis地址信息修改为自己的信息

Redis实现短信登录的企业实例分析

3.3 启动项目 启动项目后,在浏览器访问:http://localhost:8081/shop-type/list ,如果可以看到数据则证明运行没有问题

Redis实现短信登录的企业实例分析

4. 导入前端项目

4.1 导入nginx文件夹 将nginx文件夹复制到任意目录,要确保该目录不包含中文、特殊字符和空格,例如:

Redis实现短信登录的企业实例分析

4.2 运行前端项目 在nginx所在目录下打开一个CMD窗口,输入命令启动nginx:

start nginx.exe

Redis实现短信登录的企业实例分析

打开chrome浏览器,在空白页面点击鼠标右键,选择检查,即可打开开发者工具:

Redis实现短信登录的企业实例分析

然后访问: http://127.0.0.1:8080 ,即可看到页面:

Redis实现短信登录的企业实例分析

二、基于Session实现登录流程

Redis实现短信登录的企业实例分析

  • 后端将生成的验证码和用户信息保存到session中,并将sessionId返回给前端保存到cookie中

  • 用户登录时,会携带cookie向后端发起请求,后端进行校验时,从cookie中获取sessionId,通过sessionId可以从session中获取用户信息并保存到ThreadLocal中

  • 后续每个线程都有一份ThreadLocal中的用户副本信息,不同线程拿到用户信息后可以实现不同的操作,从而起到线程隔离作用

1. 发送短信验证码

Redis实现短信登录的企业实例分析

主要代码:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private IUserService userService;

    /**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // 发送短信验证码并保存验证码
        return userService.sendCode(phone, session);
    }
}
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.使用工具类校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.符合,生成验证码
        String code = RandomUtil.randomNumbers(6);

        // 4.保存验证码到 session
        session.setAttribute("code",code);

        // 5.模拟发送验证码
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回ok
        return Result.ok();
    }
}

2. 短信验证码登录、注册

Redis实现短信登录的企业实例分析

主要代码:

UserController

/**
 * 登录功能
 * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
 */
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    // 实现登录功能
    return userService.login(loginForm, session);
}

UserServiceImpl

@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)) {
        // 3.验证码不一致,则报错
        return Result.fail("验证码错误");
    }

    // 4.验证码一致,根据手机号查询用户
    User user = query().eq("phone", phone).one();

    // 5.判断用户是否存在
    if (user == null) {
        // 6.用户不存在,则创建用户并保存
        user = createUserWithPhone(phone);
    }

    // 7.保存用户信息到session中,UserDTO只包含简单的用户信息,
    // 而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
    session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

    // 8.返回ok
    return Result.ok();
}

private User createUserWithPhone(String phone) {
    // 1.创建用户
    User user = new User();
    user.setPhone(phone);
    // 随机设置昵称 user_mrkuw05lok
    user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
    // 2.保存用户
    save(user);
    return user;
}

3. 登录验证功能

用户请求登录时,会携带cookie,cookie中包含JSEESIONID

Redis实现短信登录的企业实例分析

为了避免用户请求每个controller时,每次都去校验用户信息,所以可以加拦截器

拦截器只需在用户请求访问时,校验一次后将用户信息保存到ThreadLocal中,供后续线程使用

Redis实现短信登录的企业实例分析

主要代码:

在工具类中编写ThreadLocal

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();
    }
}

在工具类中编写登录拦截器

public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 前置拦截
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @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){
            // 4.不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        // 5.存在,保存用户信息到ThreadLocal
        UserHolder.saveUser((User)user);
        // 6.放行
        return true;
    }

    /**
     * 后置拦截器
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {
        // 请求结束后移除用户,防止ThreadLocal造成内存泄漏
        UserHolder.removeUser();
    }
}

在配置类中添加拦截器配置类

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
            // 排除不需要拦截的路径
            .excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
            );
    }
}

UserController

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

三、集群的session共享问题

Redis实现短信登录的企业实例分析

四、基于Redis实现共享session的登录功能

1. 选择合适的数据结构存入Redis

  • 手机号作为key,String类型的验证码作为value

  • 用户登录时正好会提交手机号,方便通过Redis进行校验验证码

Redis实现短信登录的企业实例分析

token作为key,Hash类型的用户信息作为value

后端校验成功后,会返回token给前端,前端会将token保存到sessionStorage中(这是浏览器的存储方式),以后前端每次请求都会携带token,方便后端通过Redis校验用户信息

Redis实现短信登录的企业实例分析

前端代码:将后端返回的token保存到sessionStorage中

Redis实现短信登录的企业实例分析

前端每次请求时,都会通过拦截器将token设置到请求头中,赋值给变量authorization,后端通过authorization获取前端携带的token进行校验

Redis实现短信登录的企业实例分析

2. 发送短信验证码

修改之前代码,将验证码存入Redis

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

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.使用工具类校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.符合,生成验证码
        String code = RandomUtil.randomNumbers(6);

        // 4.保存验证码到 session
//        session.setAttribute("code",code);
        // 4.保存验证码到 redis
        // "login:code:"是业务前缀,以"login:code:" + 手机号为key,过期时间2分钟
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);

        // 5.模拟发送验证码
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回ok
        return Result.ok();
    }
}

3. 短信验证码登录、注册

  • 修改之前代码,从Redis获取验证码并校验

  • 随机生成token,保存用户信息到redis中,返回token

@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)) {
//            // 3.验证码不一致,则报错
//            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.保存用户信息到session中,UserDTO只包含简单的用户信息,而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

    // 7.保存用户信息到redis中
    // 7.1随机生成token,作为登录令牌
    // 使用hutool工具中的UUID,true表示不带“-”符号的UUID
    String token = UUID.randomUUID().toString(true);
    
    // 7.2将User对象转为Hash类型进行存储
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);   
    // 由于使用的是stringRedisTemplate,所以存入的value中的值必须都是String类型的
    // 但是UserDTO中的id是Long类型的,所以进行对象属性拷贝时,需要自定义实现转换规则
     Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
                                                     
    // 7.3存入redis, "login:token:"是业务前缀,以 "login:token:" + token作为key
    stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, userMap);
    // 7.4设置token有效期,有效期为30分钟
    stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

    // 8.返回token
    return Result.ok(token);
}

private User createUserWithPhone(String phone) {
    // 1.创建用户
    User user = new User();
    user.setPhone(phone);
    // 随机设置昵称 user_mrkuw05lok
    user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
    // 2.保存用户
    save(user);
    return user;
}

4. 解决token刷新问题

  • token刷新问题是指,用户长时间不进行界面操作时,到了过期时间,token自动失效;但是,用户一旦进行操作,就需要给token续期,即更新token过期时间

  • 为了解决token刷新问题,需要加2个拦截器

  • 第一个拦截器可以拦截所有请求,只要用户有请求就刷新token,并保存用户信息到ThreadLocal中

  • 第二个拦截器只对登录请求进行拦截,从ThreadLocal中获取用户信息进行校验

Redis实现短信登录的企业实例分析

刷新token的拦截器代码:

public class RefreshTokenInterceptor implements HandlerInterceptor {

    // 因为LoginInterceptor不是通过Spring进行管理的Bean,所以不能再LoginInterceptor中进行注入StringRedisTemplate
    // 可以通过构造方法传入StringRedisTemplate
    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 前置拦截
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @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){
//            // 4.不存在,拦截,返回401状态码
//            response.setStatus(401);
//            return false;
//        }
//        // 5.存在,保存用户信息到ThreadLocal
//        UserHolder.saveUser((UserDTO)user);
//        // 6.放行
//        return true;

        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            // 不存在,则拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        // 2.通过token获取redis中的用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
            .entries(RedisConstants.LOGIN_USER_KEY + token);

        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            // 4.用户不存在,则拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        // 5.将redis中Hash类型数据转换成UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 6.用户存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);

        // 7.刷新token有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        // 8.放行
        return true;
    }

    /**
     * 后置拦截器
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {
        // 请求结束后移除用户,防止ThreadLocal造成内存泄漏
        UserHolder.removeUser();
    }
}

登录拦截器的代码:

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser() == null) {
            // 没有,需要拦截,设置状态码
            response.setStatus(401);
            // 拦截
            return false;
        }
        // 有用户,则放行
        return true;
    }

}
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
            // 排除不需要拦截的路径
            .excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
            ).order(1);

        // token刷新的拦截器,order越小,执行优先级越高,所以token刷新的拦截器先执行
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**")
            .excludePathPatterns(
            // RefreshTokenInterceptor拦截器也需要放行"/user/code","/user/login",不然token过期后再重新登录就会一直被拦截
                "/user/code",
                "/user/login")
            .order(0);
    }
}

以上是Redis实现短信登录的企业实例分析的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:亿速云。如有侵权,请联系admin@php.cn删除
REDIS:确定其主要功能REDIS:确定其主要功能Apr 12, 2025 am 12:01 AM

Redis的核心功能是高性能的内存数据存储和处理系统。1)高速数据访问:Redis将数据存储在内存中,提供微秒级别的读写速度。2)丰富的数据结构:支持字符串、列表、集合等,适应多种应用场景。3)持久化:通过RDB和AOF方式将数据持久化到磁盘。4)发布订阅:可用于消息队列或实时通信系统。

REDIS:流行数据结构指南REDIS:流行数据结构指南Apr 11, 2025 am 12:04 AM

Redis支持多种数据结构,具体包括:1.字符串(String),适合存储单一值数据;2.列表(List),适用于队列和栈;3.集合(Set),用于存储不重复数据;4.有序集合(SortedSet),适用于排行榜和优先级队列;5.哈希表(Hash),适合存储对象或结构化数据。

redis计数器怎么实现redis计数器怎么实现Apr 10, 2025 pm 10:21 PM

Redis计数器是一种使用Redis键值对存储来实现计数操作的机制,包含以下步骤:创建计数器键、增加计数、减少计数、重置计数和获取计数。Redis计数器的优势包括速度快、高并发、持久性和简单易用。它可用于用户访问计数、实时指标跟踪、游戏分数和排名以及订单处理计数等场景。

redis命令行怎么用redis命令行怎么用Apr 10, 2025 pm 10:18 PM

使用 Redis 命令行工具 (redis-cli) 可通过以下步骤管理和操作 Redis:连接到服务器,指定地址和端口。使用命令名称和参数向服务器发送命令。使用 HELP 命令查看特定命令的帮助信息。使用 QUIT 命令退出命令行工具。

redis集群模式怎么搭建redis集群模式怎么搭建Apr 10, 2025 pm 10:15 PM

Redis集群模式通过分片将Redis实例部署到多个服务器,提高可扩展性和可用性。搭建步骤如下:创建奇数个Redis实例,端口不同;创建3个sentinel实例,监控Redis实例并进行故障转移;配置sentinel配置文件,添加监控Redis实例信息和故障转移设置;配置Redis实例配置文件,启用集群模式并指定集群信息文件路径;创建nodes.conf文件,包含各Redis实例的信息;启动集群,执行create命令创建集群并指定副本数量;登录集群执行CLUSTER INFO命令验证集群状态;使

redis怎么读取队列redis怎么读取队列Apr 10, 2025 pm 10:12 PM

要从 Redis 读取队列,需要获取队列名称、使用 LPOP 命令读取元素,并处理空队列。具体步骤如下:获取队列名称:以 "queue:" 前缀命名,如 "queue:my-queue"。使用 LPOP 命令:从队列头部弹出元素并返回其值,如 LPOP queue:my-queue。处理空队列:如果队列为空,LPOP 返回 nil,可先检查队列是否存在再读取元素。

redis集群zset怎么使用redis集群zset怎么使用Apr 10, 2025 pm 10:09 PM

Redis 集群中使用 zset:zset 是一种有序集合,将元素与评分关联。分片策略: a. 哈希分片:根据 zset 键的哈希值分布。 b. 范围分片:根据元素评分划分为范围,并将每个范围分配给不同的节点。读写操作: a. 读操作:如果 zset 键属于当前节点的分片,则在本地处理;否则,路由到相应的分片。 b. 写入操作:始终路由到持有 zset 键的分片。

redis数据怎么清空redis数据怎么清空Apr 10, 2025 pm 10:06 PM

如何清空 Redis 数据:使用 FLUSHALL 命令清除所有键值。使用 FLUSHDB 命令清除当前选定数据库的键值。使用 SELECT 切换数据库,再使用 FLUSHDB 清除多个数据库。使用 DEL 命令删除特定键。使用 redis-cli 工具清空数据。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版