5.缓存redis

redis简介

redis特点

  • 内存数据库
  • 单线程,多IO,高速读11w/s,写8w1/s
  • 支持持久化
  • 5种数据类型
  • key-vlue
  • list
  • set
  • zset
  • hash
  • 分布式锁
  • 事务
  • 集群方案
  • 开源免费
  • 业务:
  • 搜索历史
  • 热搜搜索
  • 分布式锁

Memcache

支持更丰富的数据类型

效率没有redis高

详细介绍网站

https://www.cnblogs.com/baichunyu/p/11631660.html

中文官网

redis中文官方网站

Redis默认端口:

端口6379,进程9368

Redis安装

  1. RDM

    安装RedisDesktopManager,新建连接:(1)自定义名字redis_local;(2)地址127.0.0.1 6379

  2. Redis

    Redis-x64-3.2.100压缩包解压至DevInstall,运行redis-server.exe

Redis基本命令

Redis 命令参考

  • set key1 helloword

设置键值对,键为key1,值为helloword

  • get key1

    获取键值对,获取键为key1的值

  • select 0

    切换到0库

  • keys *

    显示所有键,*可以有格式,如:k*意思为以k开头的键

  • dbsize

    数据库的大小

  • flushdb

    删除单库

  • flushall

    删除全库,删库跑路~

  • type

  • append

  • strlen key3

  • incr key1

    key1 +1

  • decr key1

    key1 -1

  • incrby key3 10

    key3 +30

  • decrby key3 10

    key3 -10

  • exists key1

    key1是否存在


字符串操作,超时,分布式锁,批量

  • getrange key4 0 -1

    截取字符串,全部。0 3:前4个字符

  • setrang key4 1 ppp

    设置指定范围内的值,设置key4,从下标1开始abcdef→apppef

  • expire key1 10

    为key1设置10后超时

  • setex key5 10 tom

    设置值的时候同时设置超时时间

  • setnx key6 aaaa

    分布式锁,无才能设值1,有就执行失败0

  • mset key7 aaa key8 bbb key9 ccc

    批量存

  • mget key7 key8 key9

    批量取

  • msetnx key10 eeee key11 ffff key 12 ggg

    所有key都不存在才能一起设置值


list操作

  • lpush catlist1 audi3 audi4 audi6

    设置list值,左设值,左取值时是先进后出,出来是6 4 3

  • lrange carlist1 0 -1

    正常取值,但是默认左list取值,没有右取值,这里的l表list

  • rpush carlist2 bwm3 bwm4 bwm5

    右list设值时,队列机制,先进先出

  • lpop carlist1

    正常出栈,但是默认左出栈,没有右出栈,这里l表list

  • lindex carlist2 1

    取lsit中第一个值

  • llen carlist2

    list中有几个元素

  • lrem catlist3 1 audi3

    从左往右删,只删一个元素,元素叫audi3

  • lrem catlist3 0 audi3

    从左往右删,把叫audi3全鲨了

  • ltrim carlist4 1 3

    从左往右把除了下标1~3全鲨了

  • rpoplpush carlist5 carlist6

    右拿左塞,从5到6


set操作

sadd set1 a b c d a b c d

往set中设置值,set无序唯一,不覆盖,追加设置值

smembers set

查看set中的值

sismember set1 a

set1中是否存在a,返回1有,返回0没有

scard set1

set1长度

srem set1 d c

删除d、c元素

spop set1

随机鲨一个set1中的元素

sdiff set1 set

显示set1中有的,但是set中没有的元素。set中有的但是set1中没有的元素不显示

sinter set1 set

set1与set的交集,显示共同存在的元素

sunion set1 set

set1与set的并集,显示二者都存在的元素并且自动去重


hash操作

hset user id u00001

设置user对象,第一个id属性值为u00001,覆盖设置值

hget user id

获取user对象id属性值

hmset user role admin gender man

批量追加属性及属性值

hmget user id role gender

批量根据属性获取属性值

hgetall user

获取user对象全部的属性及属性值

hlen user

user对象中有几个属性

hdel user gender

把user中gender属性连键带值全鲨了

hkeys user

获取user中键集

hvals user

获取user中值集

hsetnx user role root

分布式锁,user中没有role属性才能设置,否则无法设置

hincrbt user age 5

给user中age属性加5


zset操作

zadd zset1 60 tom 70 jack 50 mark 80 marry

zset设置值,可追加可覆盖

zrange zset 0 -1

查看所有值

zrange zset1 0 -1 withscores

带分值(排名权重)查看所有值

zrevrange zset1 0 -1

倒序查看所有值

zrangebyscore zset1 20 40

查看分值在20~40内的元素(包括首尾)

zrangebyscore zset1 (20 (40

查看分值在20~40内的元素(不包括首尾)

zrangebyscore zset1 20 40 limit 2 2

查看分值在20~40内的包括首尾),下标为2,往后2个的元素(分页处理)

zrem zset1 a

把叫a的元素鲨了

zrank zset1 c

显示c的下标

开源项目

微人事--烟雨江南


使用Idea操作redis

基本写法

测试类中使用时:测试类中需要加@RunWith
(SpringRunner.class)注解,才能运行

  • 使用spring自动装配的RedisTemplate对象来获得redis的操作
  • 塞进去的数据没有序列化
  • 点的操作多,麻烦
package com.lcywings.sbt;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class Springboot04RedisLcywingsApplicationTests {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
//    private RedisTemplate<Object, Object> redisTemplate;

    @Test
    public void TestRedisTemplate() {
        //  清空当前数据库所有缓存数据
        System.out.println("------ 清空当前数据库缓存 ------");
        redisTemplate.getConnectionFactory().getConnection().flushDb();

        // 使用java程序操作redis,存入数据
        System.out.println("------ 向redis添加数据 ------");
        redisTemplate.opsForValue().set("a", "1");
        redisTemplate.opsForValue().set("KH89-NUM", 29);

        // 使用java程序操作redis,存入数据
        System.out.print("------ 向redis获取数据,");
        System.out.println("数据是:" + redisTemplate.opsForValue().get("a") + " ------");

        // 操作值,进行加减操作
        long num = redisTemplate.opsForValue().increment("KH89-NUM", 2);
        System.out.println("------ 操作redis数据,加2,结果为:" + num + " ------");

    }
}

使用RedisUtil工具类

  • 需要加装RedisUtil工具类
  • 需要加装Redisconfig配置类

优点:

  • ​ 序列化
  • ​ 使用简单

RedisUtil工具类

package com.lcywings.sbt.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);

            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 加锁
     *
     * @param key
     * @param value
     * @param expire
     * @return 是否加锁成功
     */
    public boolean lock(String key, Object value, long expire) {
        if (null == value) {
            value = new Byte[]{1};
        }
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
            expire(key, expire);
            return true;
        }
        return false;
    }

    /**
     * 解锁
     *
     * @param key
     */
    public void delLock(String key) {
        redisTemplate.delete(key);
    }


    public Set<String> keys(String nameSpace) {
        return redisTemplate.keys("*" + nameSpace + "*");
    }

    /**
     * 点击收藏
     * @param key
     * @param count
     * @param ttl
     * @return
     */
    public boolean checkFreq(String key, long count, long ttl) {
        boolean exists = redisTemplate.hasKey(key);
        BoundValueOperations<String, Object> valueOps = redisTemplate.boundValueOps(key);
        Long value = valueOps.increment(1);
        if (value == null) value = count;

        if (!exists) {
            redisTemplate.expire(key, ttl, TimeUnit.SECONDS);
        }
        return value <= count;
    }
}

Redisconfig配置类

@Autowired
private RedisUtils redisUtils;

@Test
public void testRedisUtil() {
    // 使用redisUtils操作redis,存入数据
    System.out.println("------ 向redis添加数据 ------");
    redisUtils.set("a", "1");
    redisUtils.set("KH89-NUM", 29);

    // 使用redisUtils操作redis,获取数据
    log.info("------ 向redis获取数据,数据是:{} ------", redisUtils.get("a"));
    log.info("------ 向redis获取数据,数据是:{} ------", redisUtils.get("KH89-NUM"));

    // 操作值,进行加减操作
    long num = redisUtils.incr("KH89-NUM", 2);
    log.info("------ 操作redis数据,学生人数加2,结果为:{} ------", redisUtils.get("KH89-NUM"));

    // 如何将实体对象存入redis
    User user = User.builder().userId("U0001").userName("Admin").userPwd("111111").build();
    log.info("------ 用户详情:{} ------", user);

    // 将对象存入Redis
    redisUtils.set(user.getUserId(), user);

    // 获取redis中存放的用户信息
    String userJson = redisUtils.get(user.getUserId()).toString();
    log.info("------ 向redis获取的String字符串,userJson:{} ------", userJson);

    //转成JSON对象,再转成bean对象
    User userInfo = JSON.parseObject(userJson, User.class);
    log.info("------ 向redis获取数据,userInfo:{} ------", userInfo);

}

测试类使用

  • 只能使用RedisTemplate<String, Object> redisTemplate;中的泛型种类
  • 导入工具类,直接点出想用的操作
  • 数据不会出问题
@Autowired
//RedisTemplate<Object, Object> redisTemplate;
RedisTemplate<String, Object> redisTemplate;

@Autowired
private RedisUtils redisUtils;

@Test
public void testRedisUtil() {
    // 使用redisUtils操作redis,存入数据
    System.out.println("------ 向redis添加数据 ------");
    redisUtils.set("a", "1");
    redisUtils.set("KH89-NUM", 29);

    // 使用redisUtils操作redis,获取数据
    log.info("------ 向redis获取数据,数据是:{} ------", redisUtils.get("a"));
    log.info("------ 向redis获取数据,数据是:{} ------", redisUtils.get("KH89-NUM"));

    // 操作值,进行加减操作
    long num = redisUtils.incr("KH89-NUM", 2);
    log.info("------ 操作redis数据,学生人数加2,结果为:{} ------", redisUtils.get("KH89-NUM"));

    // 如何将实体对象存入redis
    User user = User.builder().userId("U0001").userName("Admin").userPwd("111111").build();
    log.info("------ 用户详情:{} ------", user);

    // 将对象存入Redis
    redisUtils.set(user.getUserId(), user);

    // 获取redis中存放的用户信息
    String userJson = redisUtils.get(user.getUserId()).toString();
    log.info("------ 向redis获取的String字符串,userJson:{} ------", userJson);

    //转成JSON对象,再转成bean对象
    User userInfo = JSON.parseObject(userJson, User.class);
    log.info("------ 向redis获取数据,userInfo:{} ------", userInfo);

}

在SpringBoot中导入静态页面&资源

小功能:

  1. 添加一个获取手机验证码的入口,浏览器输入手机号,点击获取验证码,将该手机的验证码存入redis,有效期是60s(在有效期内,使用该验证码可以返回该验证码正确或者错误,超出有效期,提示验证码失效,需要重新获取)
  2. 实现收藏功能(点一次是收藏,再点一次是取消收藏),为了防止用户恶意操作,增加限制(限制在固定的时间内,比如5s,最多点击次数,比如3次,超出次数不给点击,提示操作频繁,请稍后重新操作

依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

yml配置:

# 静态页面配置
spring:
  mvc:
    static-path-pattern: /**

  # 静态资源的默认访问路径前缀
  resources:
    static-locations: classpath:/static

  # controller返回 String后跳转到指定界面时的路径前缀
  thymeleaf:
    prefix: classpath:/templates/

静态资源放的位置:

resources的static下(新建js、css...文件)

页面放的位置:

resources的templates下

Controller

package com.lcywings.sbt.controller;

import com.lcywings.sbt.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Random;

/**
 * Created on 2021/7/25.
 * <p>
 * Author : Lcywings
 * <p>
 * Description :
 */
@Slf4j
@Controller
public class PhoneController {

    @Autowired
    RedisUtils redisUtils;


    /**
     * @author : Lcywings
     * @date : 2021/7/25 21:45
     * @acl : true
     * @description : 去首页
     */
    @RequestMapping("/go")
    public String toIndex() {
        redisUtils.del("qqqq");
        return "index";
    }

    /**
     * @author : Lcywings
     * @date : 2021/7/25 21:19
     * @acl : true
     * @description : 发送手机验证码
     */
    @GetMapping("/phoneSend")
    @ResponseBody
    public String phoneSend() {
        Random random = new Random();
        Integer phoneVal = 0;
        do {
            phoneVal = random.nextInt(10000);
        }
        while (phoneVal <= 999);
        redisUtils.set("phoneVal", phoneVal, 60000);
        log.info("------ 验证码为:{} ------", phoneVal);
        return phoneVal + "";
    }

    /**
     * @author : Lcywings
     * @date : 2021/7/25 21:21
     * @acl : true
     * @description : 手机验证码校验
     */
    @GetMapping("/login")
    @ResponseBody
    public String phoneVal(@RequestParam("val") String val) {
        if (null == redisUtils.get("phoneVal"))
            return "overtime";
        if (val.equals(redisUtils.get("phoneVal").toString()))
            return "yes";
        else
            return "no";
    }

    /**
     * @author : Lcywings
     * @date : 2021/7/25 23:06
     * @acl : true
     * @description : 点击收藏
     */
    @GetMapping("/collect")
    @ResponseBody
    public String collect() {
        // 模拟数据库收藏状态
        if (null == redisUtils.get("collectState")) {
            redisUtils.set("collectState", 0);
        }
        Integer collectState = Integer.valueOf(redisUtils.get("collectState") + "");

        if (redisUtils.checkFreq("qqqq", 3, 5)) {
            if (collectState == 1) {
                redisUtils.set("collectState", 0);
                return "0";
            } else {
                redisUtils.set("collectState", 1);
                return "1";
            }
        } else {
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                redisUtils.del("qqqq");
            });
            return "3";
        }
    }

}

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title></title>
</head>
<body>
<table>
    <tr>
        <td>手机号:<input type="tel" name="phone"/>
            <button name="phoneVal" id="phoneVal" onclick="sendPhone()">验证</button>
        </td>
    </tr>
    <tr>
        <td>验证码:<input type="text" name="val" id="val"/></td>
    </tr>
    <tr>
        <td>
            <button name="login" id="login" onclick="login()" style="width: 80%;align-self: center">登录</button>
        </td>
    </tr>
    <tr>
        <td>
            <input style="font-size: 1.6875rem;" type="button" name="collect" id="collect" value="点击收藏"
                   onclick="collect()"/>
        </td>
    </tr>
</table>
</body>
<script type="text/javascript" src="/js/jquery-3.4.1.min.js"></script>
<script type="text/javascript">


    function sendPhone() {
        $.get("/phoneSend", function (data) {
            alert("验证码发送成功!" + data)
        });
    }

    function login() {
        var val = $("#val").val();
        alert("val:" + val);
        $.get("/login", {"val": val}, function (data) {
            if (data == "yes")
                alert("登录成功!")
            else if (data == "no")
                alert("登录失败!")
            else if (data == "overtime") {
                alert("验证码超时!")
            }
        });
    }

    function collect() {
        $.get("/collect", function (data) {
            if (data == "1")
                $("#collect").attr("value", "已收藏");
            else if (data == "0")
                $("#collect").attr("value", "点击收藏");
            else if (data == "3")
                alert("操作次数太频繁!")
        });
    }
</script>
</html>

Q.E.D.