Skip to content

Redis 学习笔记

Redis 安装及配置笔记

Redis下载

下载地址:Releases · microsoftarchive/redis · GitHub

Redis 配置

解压压缩包

将路径添加至环境变量

在当前路径下按shift + 鼠标右键,点击打开PowerShell

输入命令 redis-server.exe redis.windows.conf

常用命令

shell
安装服务:redis-server --service-install
卸载服务:redis-server --service-uninstall
开启服务:redis-server --service-start
停止服务:redis-server --service-stop
服务端启动时重命名:redis-server --service-start --service-name Redis1

Redis 连接

shell
redis-cli -h host -p port -a password

image-20221119225040295

Redis 数据结构

String 类型

设置指定 key 的值SET key value

shell
set name gettler

获取指定 key 的值 GET key

shell
get name

image-20221119225721347

Hash 类型

List类型

Set 类型

Sorted 类型

Redis 通用命令

Redis 的 Java 客户端

image-20221119232636235

image-20221119234101427

image-20221119233529364

请定义一个用户信息结构体,然后使用 fastjson 对用户信息对 象进行序列化和反序列化,再使用 jedis 对 Redis 缓存的用户信息进行存和取。

User类

java
package com.gettler.base;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author Gettler
 * @date 2022/11/20
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
}

Jedis连接工厂

java
package com.gettler.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author Gettler
 * @date 2022/11/20
 */
public class JedisConnectionFactory {
    private static final JedisPool JEDIS_POOL;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMinIdle(0);
        jedisPoolConfig.setMaxIdle(8);
        jedisPoolConfig.setMaxTotal(8);
        jedisPoolConfig.setMaxWaitMillis(1000);
        JEDIS_POOL = new JedisPool(jedisPoolConfig, "localhost", 6379, 1000);
    }

    public static Jedis getJedis() {
        return JEDIS_POOL.getResource();
    }
}

主类

java
package com.gettler.base;

import com.alibaba.fastjson.JSON;
import com.gettler.util.JedisConnectionFactory;
import redis.clients.jedis.Jedis;

/**
 * @author Gettler
 * @date 2022/11/21
 */
public class Homework1 {
    public static void main(String[] args) {
        Jedis jedis = JedisConnectionFactory.getJedis();
        jedis.auth("73748156");
        jedis.select(0);
        User user = new User("Gettler", 21);
        jedis.set("user:1", JSON.toJSONString(user));
        User user2 = new User("lyl", 21);
        jedis.set("user:2", user2.toString());
        System.out.println("user1 :" + jedis.get("user:1"));
        System.out.println("user2 :" + jedis.get("user:2"));
        jedis.close();
    }
}

image-20221121095302013

项目实战

Redis 实现验证码登录

使用手机+验证码登录时生成验证码

使用正则表达式校验手机号,使用随机数工具生成六位数验证码,并将其存入redis中。

java
// 校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
  return Result.fail("手机号格式错误");
  }
  // 符合则生成验证码
  String code=RandomUtil.randomNumbers(6);
  // 保存验证码
  stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL,TimeUnit.MINUTES);
  // TODO 发送验证码
  log.debug("发送验证码成功,验证码:"+code);
  return Result.ok();

校验验证码

在redis中获取以手机号为键的值,以此校验前端传回的验证码

将获取到的user对象转为Map对象,并以随机字符串token为键存入redis中,再将token传回前端。

java
String phone=loginForm.getPhone();
  // 校验手机号
  if(RegexUtils.isPhoneInvalid(phone)){
  return Result.fail("手机号格式错误");
  }
  // 校验验证码
  String code=loginForm.getCode();
  String cacheCode=stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
  if(!code.equals(cacheCode)){
  return Result.fail("验证码错误");
  }
  // 判断用户是否存在
  User user=query().eq("phone",phone).one();
  // 不存在,创建新用户
  if(user==null){
  user=createUserWithPhone(phone);
  }
  String token=UUID.randomUUID().toString();
  // 将用户信息存到 Redis 里
  UserDTO userDTO=BeanUtil.copyProperties(user,UserDTO.class);
  String tokenKey=LOGIN_USER_KEY+token;
  //        stringRedisTemplate.opsForHash().putAll(tokenKey, BeanUtil.beanToMap(userDTO));
  // 上方写法Long 转 String 会报错,需要用下方写法
  stringRedisTemplate.opsForHash()
  .putAll(tokenKey,BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create()
  .setIgnoreNullValue(true)
  .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString())));
  stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.SECONDS);
  return Result.ok(token);

编写一个拦截器,在发送请求后,刷新token在redis中所对应键值对的有效期

java
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Gettler
 * @date 2023/1/24
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

  private StringRedisTemplate stringRedisTemplate;

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

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String token = request.getHeader("authorization");
    if (StrUtil.isBlank(token)) {
      return true;
    }
    String key = RedisConstants.LOGIN_USER_KEY + token;
    Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
    if (userMap.isEmpty()) {
      return true;
    }
    UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
    UserHolder.saveUser(userDTO);
    // 刷新token有效期
    stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
    return true;
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    UserHolder.removeUser();
  }
}

还有在访问需要登录才能访问的路径时的登录拦截器,统一判断是否有权限访问

java
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Gettler
 * @date 2023/1/24
 */
public class LoginInterceptor implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (UserHolder.getUser() == null) {
      response.setStatus(401);
      return false;
    }
    return true;
  }
}

编写配置文件将拦截器注册,并将StringRedisTemplate注入到容器中

java
import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * @author Gettler
 * @date 2023/1/25
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

  @Resource
  StringRedisTemplate stringRedisTemplate;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 先添加先执行,也可以使用order方法指定优先级,数字越小优先级越高
    registry.addInterceptor(new LoginInterceptor())
            .excludePathPatterns("/shop/**", "/voucher/**", "/shop-type/**", "/blog/hot", "/upload/**",
                                 "/user" + "/code", "/user/login").order(1);
    registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
  }
}