redis的String和hash类型使用和代码实现

一 Redis命令

Redis支持五种数据(结构)类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)等

常用命令key管理

keys  *	: 返回满足的所有键 ,可以模糊匹配 比如 keys  abc* 代表 abc 开头的 key
exists key :是否存在指定的key,存在返回1,不存在返回0
expire key second:设置某个key的过期时间 时间为秒
del key:删除某个key
ttl key:查看剩余时间,当key不存在时,返回 -2;存在但没有设置剩余生存时间时,返回 -1,否则,以秒为单位,返回 key 的剩余生存时间。
persist key:取消过去时间
PEXPIRE key milliseconds 修改key 的过期时间为毫秒
select :  选择数据库  数据库为0-15(默认一共16个数据库) s
设计成多个数据库实际上是为了数据库安全和备份
move key dbindex : 将当前数据中的key转移到其他数据库
randomkey:随机返回一个key
rename key key2:重命名key
echo:打印命令
dbsize:查看数据库的key数量
info:查看数据库信息
config get * 实时传储收到的请求,返回相关的配置	
flushdb :清空当前数据库
flushall :清空所有数据库

DEL key

该命令用于在 key 存在时删除 key。 

EXISTS key

检查给定 key 是否存在。 

EXPIRE key seconds

为给定 key 设置过期时间(以秒计)。 

PEXPIRE key milliseconds

设置 key 的过期时间以毫秒计。

TTL key

以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live) 

PTTL key

以毫秒为单位返回 key 的剩余的过期时间。

KEYS pattern

查找所有符合给定模式( pattern)的 key 。 
keys 通配符     获取所有与pattern匹配的key,返回所有与该匹配 
  通配符: 
         *  代表所有 
         ? 表示代表一个字符 

RENAME key newkey

修改Key的名称

MOVE key db

将当前数据库的 key 移动到给定的数据库 db 当中 

TYPE key

返回 key 所储存的值的类型

应用场景

EXPIRE key seconds

1、限时的优惠活动信息
2、网站数据缓存(对于一些需要定时更新的数据,例如:积分排行榜)
3、手机验证码
4、限制网站访客访问频率(例如:1秒钟最多访问5次)

Key的命名建议

redis单个key允许存入512M大小

  • 1.key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;

  • 2.key也不要太短,太短的话,key的可读性会降低;

  • 3.在一个项目中,key最好使用统一的命名模式,例如user:123:password

  • 4.key名称区分大小写

二 Redis数据类型

String类型

简介

string类型是Redis最基本的数据类型,一个键最大能存储512MB。

string 数据结构是简单的key-value类型,value其不仅是string,也可以是数字,是包含很多种类型的特殊类型,

string类型是二进制安全的。意思是redis的string可以包含任何数据。
比如序列化的对象进行存储,比如一张图片进行二进制存储,比如一个简单的字符串,数值等等。

String命令

赋值语法:
  SET KEY_NAME   VALUE: (说明:多次设置name会覆盖) (Redis SET 命令用于设置给定 key 的值。如果 key 已经存储值, SET 就覆写旧值,且无视类型)
  
命令:
  SETNX key1 value:(not exist) 如果key1不存在,则设值 并返回1。如果key1存在,则不设值并返回0;(解决分布式锁 方案之一,只有在 key 不存在时设置 key 的值。Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值)
  
  SETEX key1 10 lx :(expired) 设置key1的值为lx,过期时间为10秒,10秒后key1清除(key也清除)
 
  SETRANGE string range value: 替换字符串
  

 
取值语法:
 GET KEY_NAME :Redis GET命令用于获取指定 key 的值。如果 key 不存在,返回 nil 。如果key 储存的值不是字符串类型,返回一个错误。
 GETRANGE key start end :用于获取存储在指定 key 中字符串的子字符串。字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)

GETBIT key offset :对 key 所储存的字符串值,获取指定偏移量上的位(bit)

GETSET语法:  GETSET  KEY_NAME  VALUE :Getset 命令用于设置指定 key 的值,并返回 key 的旧值,当 key 不存在时,返回 nil 

STRLEN key :返回 key 所储存的字符串值的长度


删值语法:
DEL KEY_Name :删除指定的KEY,如果存在,返回值数字类型。

批量写:MSET k1 v1 k2 v2 ... 一次性写入多个值
批量读:MGET k1 k2 k3

GETSET name value :一次性设值和读取(返回旧值,写上新值)

自增/自减:
INCR KEY_Name :Incr 命令将 key 中储存的数字值增1。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作 
 自增:INCRBY KEY_Name :增量值 Incrby 命令将 key 中储存的数字加上指定的增量值 
 自减:DECR KEY_NAME       或    DECYBY KEY_NAME 减值 :DECR 命令将 key 中储存的数字减1
 
 :(注意这些 key 对应的必须是数字类型字符串,否则会出错,)

字符串拼接:APPEND  KEY_NAME VALUE 
 :Append 命令用于为指定的 key 追加至未尾,如果不存在,为其赋值

字符串长度 :STRLEN key

应用场景

  • 1、String通常用于保存单个字符串或JSON字符串数据
  • 2、因String是二进制安全的,所以你完全可以把一个图片文件的内容作为字符串来存储
  • 3、计数器(常规key-value缓存应用。常规计数: 微博数, 粉丝数)

INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果。假如,在某种场景下有3个客户端同时读取了mynum的值(值为2),然后对其同时进行了加1的操作,那么,最后mynum的值一定是5。
不少网站都利用redis的这个特性来实现业务上的统计计数需求。

Hash类型

简介

Hash类型是String类型的field和value的映射表,或者说是一个String集合。hash特别适合用于存储对象,相比较而言,将一个对象类型存储在Hash类型要存储在String类型里占用更少的内存空间,并对整个对象的存取。
可以看成具有KEY和VALUE的MAP容器,该类型非常适合于存储值对象的信息, 
如:uname,upass,age等。该类型的数据仅占用很少的磁盘空间(相比于JSON)。
Redis 中每个 hash 可以存储 2的32次方  - 1 键值对(40多亿)

Hash命令

常用命令

赋值语法: 
 HSET KEY  FIELD VALUE    :为指定的KEY,设定FILD/VALUE  
 HMSET KEY FIELD VALUE [FIELD1,VALUE1]…… :同时将多个 field-value (域-值)对设置到哈希表 key 中。 
 
取值语法: 
  HGET KEY FIELD   :获取存储在HASH中的值,根据FIELD得到VALUE
  HMGET KEY field[field1]      :获取key所有给定字段的值 
  HGETALL KEY      :返回HASH表中所有的字段和值 
	
HKEYS KEY  :获取所有哈希表中的字段
HLEN KEY   :获取哈希表中字段的数量

删除语法: 
   HDEL KEY field1[field2]    :删除一个或多个HASH表字段 

其它语法:
HSETNX key field value :只有在字段 field 不存在时,设置哈希表字段的值

HINCRBY key field increment :为哈希表 key 中的指定字段的整数值加上增量 increment 。

HINCRBYFLOAT key field increment  :为哈希表 key 中的指定字段的浮点数值加上增量 increment 。

HEXISTS key field  :查看哈希表 key 中,指定的字段是否存在

应用场景

Hash的应用场景:(存储一个对象数据)
1、 常用于存储一个对象
2、 为什么不用string存储一个对象?

	hash是最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成hashmap存放在redis中。
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:
    第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。
    第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

总结:
Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,

并提供了直接存取这个Map成员的接口

三 常用的redis客户端介绍以及对比

Jedis api 在线网址:http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html

redisson 官网地址:https://redisson.org/

redisson git项目地址:https://github.com/redisson/redisson

lettuce 官网地址:https://lettuce.io/

lettuce git项目地址:https://github.com/lettuce-io/lettuce-core

首先,在spring boot2之后,对redis连接的支持,默认就采用了lettuce。这就一定程度说明了lettuce 和Jedis的优劣。

概念:

Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
Redisson:实现了分布式和可扩展的Java数据结构。
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

优点:

Jedis:比较全面的提供了Redis的操作特性
Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列
Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作

可伸缩:

Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
lettuce能够支持redis4,需要java8及以上。
lettuce是基于netty实现的与redis进行同步和异步的通信。

lettuce和jedis比较:

jedis使直接连接redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个jedis实例增加物理连接 ;
lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,StatefulRedisConnection是线程安全的,所以一个连接实例可以满足多线程环境下的并发访问,当然这也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

总结:

优先使用Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用,因为Redisson本身对字符串的操作支持很差。
在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增ID,由于web应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s的并发,数据库可能由行锁变成表锁,性能下降会厉害。那相对而言,redis的分布式锁,相对而言,是个很好的选择,redis官方推荐使用的Redisson就提供了分布式锁和相关服务。 

在官方网站列一些Java客户端访问,有:Jedis/Redisson/Jredis/JDBC-Redis等,其中官方推荐使用Jedis和Redisson。常用Jedis

四 SpringBoot整合Jedis

简介

​ 我们在使用springboot搭建微服务的时候,在很多时候还是需要redis的高速缓存来缓存一些数据,存储一些高频率访问的数据,如果直接使用redis的话又比较麻烦,在这里,我们使用jedis来实现redis缓存来达到高效缓存的目的

引入Jedis依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

因为 SpringBoot 内默认引用了jedis版本。

所以我们直接引入jedis 依赖 无需在配置 jedis的版本号了。

application.yml

例如 在application.yml 中配置 如下信息:

spring:
  redis:
    port: 6379
    password: 123456
    host: 192.168.230.128
    jedis:
      pool:
        max-idle: 6
        max-active: 10
        min-idle: 2
    timeout: 2000
    database: 0

编写Config

创建类:com.han.config.JedisConfig

package com.han.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;

@Configuration
@Slf4j
public class JedisConfig {

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Bean
    public JedisPool jedisPool(){

        //JedisPool的强制配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxTotal(maxActive);

        //JedisPool的属性配置
        JedisPool jedisPool = new JedisPool(poolConfig,host,port,timeout,password);

        log.info("JedisPool连接成功:"+host+"\t"+port);

        return jedisPool;
    }

}

测试配置

@SpringBootTest
@Slf4j
class Ch3JedisApplicationTests {

    @Autowired
    private JedisPool jedisPool;

    @Autowired
    private JedisService jedisService;

    @Test
    void contextLoads() {

        String key = "java2007";
        //获取jedis连接
        Jedis jedis = jedisPool.getResource();

        if (jedis.exists(key)){
            String value = jedis.get(key);
            log.info("获取jedis里面的数据");
            System.out.println(value);
        }else{
            //selectUserByName();
            String value = "2007班全体同学";
            log.info("获取mysql里面的数据");
            jedis.set(key,value);
            System.out.println(value);
        }
    }

封装工具类

JedisUtil

package com.config.jedis;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
 * @ClassName JedisUtils
 * @Description TODO
 * @Author guoweixin
 * @Version 1.0
 */
@Component
public class JedisUtils {
    @Autowired
    private JedisPool jedisPool;

    /**
     * 获取Jedis资源
     */
    public Jedis getJedis(){
        return jedisPool.getResource();
    }
    /**
     * 释放Jedis连接
     */
    public void close(Jedis jedis){
        if(jedis!=null){
            jedis.close();
        }
    }
    .......................

}

测试

Jedis操作String类型

业务JedisServiceImpl类

@Service
@Slf4j
public class JedisServiceImpl implements JedisService {
    @Autowired
    private JedisPool jedisPool;

    public String getString(String key){
        //获取数据库连接
        Jedis jedis = jedisPool.getResource();
        String value =null;
        //如果key存在就获取,如果不存在就去MySQL查询放到jedis里面在返回数据
        if (jedis.exists(key)){
            value = jedis.get(key);
            log.info("获取Redis里面的key");
            return value;
        }else{
            //selectUserByName();
            value = "杨宇星想要找个富婆!";
            log.info("获取mysql里面的数据");
            jedis.set(key,value);
            return value;
        }
    }

单元测试

@SpringBootTest
@Slf4j
class Ch3JedisApplicationTests {

    @Autowired
    private JedisPool jedisPool;

    @Autowired
    private JedisService jedisService;

    @Test
    void string(){
        String value = jedisService.getString("yyx");
        System.out.println(value);
    }
}

Jedis操作Hash类型

业务JedisServiceImpl类

@Service
@Slf4j
public class JedisServiceImpl implements JedisService {
    @Autowired
    private JedisPool jedisPool;

 /**
     * 测试jedis操作hash类型
     * 根据用户id查询用户信息
     * 先判断Redis中是否存在
     * 如果不存在,就去数据库中查询,并保存到Redis中
     * 如果存在,直接查询Redis 并返回
     * @return
     */
    public User getHash(Integer id){
        //获取jedis数据连接
        Jedis jedis = jedisPool.getResource();

        User user = new User();
        String key = "user:"+id;
        if (jedis.exists(key)){
            Map<String, String> map = jedis.hgetAll(key);
            user.setId(Integer.parseInt(map.get("id")));
            user.setName(map.get("name"));
            user.setAge(Integer.parseInt(map.get("age")));
            user.setRemark(map.get("remark"));
            log.info("查询Redis里面的数据");
            return user;
        }else{
            //selectUserById();
            user.setId(id);
            user.setName("杨宇星");
            user.setAge(24);
            user.setRemark("先要找个富婆");
            log.info("查询mysql里面的数据");
            Map<String,String> map = new HashMap<>();
            map.put("id",Integer.toString(user.getId()));
            map.put("name",user.getName());
            map.put("age",Integer.toString(user.getAge()));
            map.put("remark",user.getRemark());
            jedis.hset(key,map);
            log.info("成功存入Redis");
            return user;
        }

    }

单元测试

@SpringBootTest
@Slf4j
class Ch3JedisApplicationTests {

    @Autowired
    private JedisPool jedisPool;

    @Autowired
    private JedisService jedisService;
    @Test
    void hash(){
        User user = jedisService.getHash(2);
        System.out.println(user.toString());
    }
}

五 SpringBoot2.x中redis使用(lettuce)

Jedis–》进一步做了封装。 --》RedisTemplate

JDBCTemplate

RestTemplate

java代码操作Redis,需要使用Jedis,也就是redis支持java的第三方类库
注意:Jedis2.7以上的版本才支持集群操作

maven配置

新建SpringBoot2.0.3的WEB工程,在MAVEN的pom.xml文件中加入如下依赖

<dependencies>
       <!--默认是lettuce客户端-->
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- redis依赖commons-pool 这个依赖一定要添加 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
		<!-- 测试库依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  </dependencies>

hibernate–>JPA–>SpringData

配置文件配置

application.yml

spring:
  redis:
    port: 6379
    password: 123456
    host: 192.168.230.128
    database: 0 #redis的0号数据库
    timeout: 2000 #超时时间
    lettuce:
      pool:
        max-active: 10
        max-idle: 8 
        min-idle: 2

redis配置类

JdbcTemplate–>JDBC 进一步封装。

RedisTemplate–>redis进行了进一步封装 (lettuce)

简介

编写缓存配置类RedisConfig用于调优缓存默认配置,RedisTemplate<String, Object>的类型兼容性更高

大家可以看到在redisTemplate()这个方法中用JacksonJsonRedisSerializer更换掉了Redis默认的序列化方式:JdkSerializationRedisSerializer

spring-data-redis中序列化类有以下几个:

GenericToStringSerializer:可以将任何对象泛化为字符创并序列化
Jackson2JsonRedisSerializer:序列化Object对象为json字符创(与JacksonJsonRedisSerializer相同)
JdkSerializationRedisSerializer:序列化java对象
StringRedisSerializer:简单的字符串序列化

JdkSerializationRedisSerializer序列化被序列化对象必须实现Serializable接口,被序列化除属性内容还有其他内容,长度长且不易阅读,默认就是采用这种序列化方式

存储内容如下:

“\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c \n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00\x14t\x00\x05user1”

JacksonJsonRedisSerializer序列化,被序列化对象不需要实现Serializable接口,被序列化的结果清晰,容易阅读,而且存储字节少,速度快

存储内容如下:

“{“userName”:“guoweixin”,“age”:20}”

StringRedisSerializer序列化

一般如果key、value都是string字符串的话,就是用这个就可以了

RedisConfig类

package com.han.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;

/**
 * @ClassName RedisConfig
 * @Description TODO
 * @Author guoweixin
 * @Version 1.0
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 自定义缓存key的生成策略。默认的生成策略是看不懂的(乱码内容) 通过Spring 的依赖注入特性进行自定义的配置注入并且此类是一个配置类可以更多程度的自定义配置
     *
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * 缓存配置管理器
     */
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory factory) {
        //以锁写入的方式创建RedisCacheWriter对象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
        //创建默认缓存配置对象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
        return cacheManager;
    }

    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
        RedisTemplate<String,Object> template = new RedisTemplate <>();
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式。
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

代码示例

RedisServiceImpl

测试String类型
@Service
@Slf4j
public class StringServiceImpl {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<String, Object> string;

    @Resource(name = "redisTemplate")
    private HashOperations<String,String,User> hash;

    public String getString(String key){

        if(redisTemplate.hasKey(key)){
            log.info("从Redis中获取数据");
            String result = (String) string.get(key);
            return result;
        }else {
            log.info("从MySQL中获取数据");
            String value = "杨宇星喜欢男的";
            string.set(key,value);
            return value;
        }
    }
测试Hash类型
@Service
@Slf4j
public class StringServiceImpl {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<String, Object> string;

    @Resource(name = "redisTemplate")
    private HashOperations<String,String,User> hash;
    
  public User getHash(String id){
        if (hash.hasKey("user",id)){
            log.info("从Redis中获取数据");
            User user = hash.get("user", id);
            return user;
        }else{
            log.info("从MySQL中获取数据");
            User user = new User();
            user.setAge("20");
            user.setId(id);
            user.setName("杨宇星");
            user.setRemark("....12313...");
            hash.put("user",id,user);
            return user;
        }
    }
    

hash类型代码示例

package com.qfjy.redis.demo.service.impl;

import com.qfjy.redis.demo.service.HashCacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

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

@Service("hashCacheService")
public class HashCacheServiceImpl implements HashCacheService {
    private final static Logger log = LoggerFactory.getLogger(HashCacheServiceImpl.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取MAP中的某个值
     * @param key  键
     * @param item 项
     * @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);
    }

    /**
     * 以map集合的形式添加键值对
     * @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;
        }
    }

    /**
     * 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 long hincr(String key, String item, long by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

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

    /**
     * 获取指定变量中的hashMap值。
     * @param key
     * @return 返回LIST对象
     */
    @Override
    public List<Object> values(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     *  获取变量中的键。
     * @param key
     * @return 返回SET集合
     */
    @Override
    public Set<Object> keys(String key) {
        return  redisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取变量的长度。
     * @param key 键
     * @return  返回长度
     */
    @Override
    public long size(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 以集合的方式获取变量中的值。
     * @param key
     * @param list
     * @return 返回LIST集合值
     */
    @Override
    public List multiGet(String key, List list) {
        return redisTemplate.opsForHash().multiGet(key,list);
    }

    /**
     *  如果变量值存在,在变量中可以添加不存在的的键值对
     *  如果变量不存在,则新增一个变量,同时将键值对添加到该变量。
     * @param key
     * @param hashKey
     * @param value
     */
    @Override
    public void putIfAbsent(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().putIfAbsent(key,hashKey,value);
    }

    /**
     * 匹配获取键值对,ScanOptions.NONE为获取全部键对,ScanOptions.scanOptions().match("map1").build()
     * 匹配获取键位map1的键值对,不能模糊匹配。
     * @param key
     * @param options
     * @return
     */
    @Override
    public Cursor<Map.Entry<Object, Object>> scan(String key, ScanOptions options) {
        return  redisTemplate.opsForHash().scan(key,options);
    }

    /**
     * 删除变量中的键值对,可以传入多个参数,删除多个键值对。
     * @param key 键
     * @param hashKeys MAP中的KEY
     */
    @Override
    public void delete(String key, String... hashKeys) {
        redisTemplate.opsForHash().delete(key,hashKeys);
    }

    public boolean expire(String key, long seconds) {
        return redisTemplate.expire(key,seconds,TimeUnit.SECONDS);
    }

    /**
     * 删除
     * @param keys
     */
    @Override
    public void del(String... keys) {
        if (keys != null && keys.length > 0) {
            if (keys.length == 1) {
                redisTemplate.delete(keys[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(keys));
            }
        }
    }

    @Override
    public long getExpire(String key) {
        return 0;
    }
}

六 作业

手机验证功能

需求描述:

用户在客户端输入手机号,点击发送后随机生成4位数字码。有效期为20秒。
输入验证码,点击验证,返回成功或者失败。且每个手机号在1分钟内只能验证3次。并给相应信息提示(并限制2分钟验证)

代码实现
service层实现

package com.han.service.impl;

import com.han.entity.Code;
import com.han.entity.Verification;
import com.han.service.RegisterVerificationService;
import com.han.util.JedisUtils;
import com.han.util.VerificationCodeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Service
public class RegisterVerificationServiceImpl implements RegisterVerificationService {

    @Autowired
    private JedisUtils jedisUtils;

    /**
     * 获取验证码
     * 判断用户是否获取过验证码
     * 如果获取过超过3次就限制5分钟内不容许获取
     * 如果没有获取过就获取验证码,把验证码存入Redis设置时间20秒
     *
     * @return
     */
    public Code getCode(String phone, String ip) {
        //记录获取次数的key
        String countKey = ip + ":code:count";
        Jedis jedis = jedisUtils.getJedis();

        Code c = new Code();
        //判断手机是否获取过验证
        if (!jedis.exists(countKey)) {
            jedis.setex(countKey, 5 * 60, "1");
        } else {
            if (Integer.parseInt(jedis.get(countKey)) >= 5) {
                c.setCount(Integer.parseInt(jedis.get(countKey)));
                return c;
            }

            //获取一次,次数加一
            jedis.incrBy(countKey, 1);
        }

        //获取验证码并存入Redis设置存在时间20s返回验证码
        String code = VerificationCodeUtils.getRandomString(4);
        jedis.setex(phone, 40, code);
        Integer count = Integer.parseInt(jedis.get(countKey));
        c.setCode(code);
        c.setCount(count);

        //关闭连接
        jedisUtils.close(jedis);
        return c;
    }

    /**
     * 判断验证码是否正确
     * 正确返回1
     * 错误返回0
     *
     * @param code
     * @return
     */
    public Verification verificationCode(String phone, String code, String ip) {

        String countKey = ip + ":code:count";

        String codeRedis = null;
        String count = null;

        String verificationCount = phone + ":verification:count";

        Jedis jedis = jedisUtils.getJedis();
        Verification v = new Verification();

        //判断是否验证过
        if (!jedis.exists(verificationCount)) {
            jedis.setex(verificationCount, 60, "1");
        }

        if (jedis.exists(phone)) {
            //获取Redis里面存储的code
            codeRedis = jedis.get(phone);

            //给记录验证次数的key加一
            jedis.incrBy(verificationCount, 1);
            //判断验证码正确
            if (code.equals(codeRedis)) {
                count = jedis.get(verificationCount);

                jedis.del(verificationCount, phone, countKey);

                v.setStatus(1);
                v.setVerificationCount(Integer.parseInt(count));
                jedisUtils.close(jedis);
                return v;
            }/*else {
                count = jedis.get(verificationCount);
                v.setStatus(0);
                v.setVerificationCount(Integer.parseInt(count));
                jedisUtils.close(jedis);
                return v;
            }*/
        }

        count = jedis.get(verificationCount);
        v.setStatus(0);
        v.setVerificationCount(Integer.parseInt(count));
        jedisUtils.close(jedis);
        return v;

    }

}

util层实现

package com.han.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Component
public class JedisUtils {

    @Autowired
    private JedisPool jedisPool;

    /**
     * 获取jedis的资源
     *
     * @return
     */
    public Jedis getJedis() {
        return jedisPool.getResource();
    }

    /**
     * 释放资源
     */
    public void close(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }


}

package com.han.util;

import java.util.Random;

public class VerificationCodeUtils {

    /*
     * 随机字符字典
     */
    private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
            'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

    /*
     * 随机数
     */
    private static Random random = new Random();

    /**
     * 获取长度为codeLength的随机字符
     *
     * @param codeLength
     * @return
     */
    public static String getRandomString(int codeLength) {
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < codeLength; i++) {
            buffer.append(CHARS[random.nextInt(CHARS.length)]);
        }
        return buffer.toString();
    }

}

entity层实现

package com.han.entity;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;

@Data
public class Code implements Serializable {
    private String code;
    //获取验证码的次数
    private Integer count;
}

package com.han.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class Verification implements Serializable {
    //获取当前验证的状态
    private Integer status;
    //验证登录的次数
    private Integer verificationCount;
}

config层配置

package com.han.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;

@Configuration
@Slf4j
public class JedisConfig {

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Bean
    public JedisPool jedisPool() {
        //JedisPoll的前置配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMaxTotal(maxActive);

        //Jedis的属性配置
        JedisPool jedisPool = new JedisPool(poolConfig, host, port, timeout, password);

        log.info("JedisPool连接成功:" + host + "\t" + port);

        return jedisPool;
    }

}

controller层实现

package com.han.controller;

import com.han.entity.Code;
import com.han.entity.Verification;
import com.han.service.RegisterVerificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.crypto.spec.PSource;
import javax.servlet.http.HttpServletRequest;

@Controller
public class RegisterVerificationController {

    @Autowired
    private RegisterVerificationService registerVerificationService;

    @ResponseBody
    @RequestMapping(value = "/getCode")
    public Object getCode(String phone, HttpServletRequest request) {
        String ip = request.getRemoteAddr();
        System.out.println(ip);
        Code code = registerVerificationService.getCode(phone, ip);
        System.out.println("验证码:---->>>>" + code.getCode());
        return code;
    }

    @ResponseBody
    @RequestMapping(value = "/verificationCode")
    public Object verificationCode(String phone, String code, HttpServletRequest request) {
        String ip = request.getRemoteAddr();
        Verification verification = registerVerificationService.verificationCode(phone, code, ip);
        return verification;
    }


}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta http-equiv="Pragma" content="no-cache">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
    <meta name="format-detection" content="telephone=yes"/>
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    <title>绑定信息</title>
    <!-- Bootstrap core CSS-->
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          th:href="@{https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css}">
    <style type="text/css">
        body {
            margin: 0;
            padding: 0;
        }

        .modal_content {
            padding: 30px;
            display: flex;
            justify-content: center;
            flex-direction: column;
        }

        .modal_content > div {
            margin-bottom: 20px;
        }

        .modal_content > h5:first-child {
            margin: 30px 0px;
        }

        #dialog label {
            color: #666;
        }

        #phone1 {
            display: block;
            width: 100%;
            height: 70px;
            background: none;
            padding-top: 30px;
            border: 0;
            outline: none;
            text-align: center;
            margin-top: -30px;
            font-size: 16px;
            border-bottom: 1px solid rgba(0, 0, 0, .2);
            border-radius: 0;
        }

        .code1 {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            width: 100%;
            height: 70px;
            background: none;
            padding-top: 30px;
            margin-top: -30px;
            font-size: 16px;
            border-bottom: 1px solid rgba(0, 0, 0, .2);
            border-radius: 0;
        }

        #code1 {
            width: calc(100% - 90px);
            height: 55px;
            background: none;
            padding-top: 20px;
            border: 0;
            outline: none;
            text-align: center;
            margin-top: -20px;
            font-size: 16px;
        }

        #btnSendCode1 {
            width: 90px;
            height: 30px;
            padding: 0 5px;
            margin: 0;
            font-size: 14px;
            text-align: center;
            background: transparent;
            border-radius: 30px;
            color: #a07941;
            border-color: #a07941;

        }

        ::-webkit-input-placeholder { /* WebKit browsers */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        :-moz-placeholder { /* Mozilla Firefox 4 to 18 */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        ::-moz-placeholder { /* Mozilla Firefox 19+ */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        :-ms-input-placeholder { /* Internet Explorer 10+ */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        .next {
            text-align: center;
            margin: 20px 0;
        }

        .next button {
            width: 100%;
            height: 45px;
            padding: 0;
            margin: 0;
            background: #007BFF;
            color: #fff;
            border: 0;
            outline: none;
            border-radius: 3px;
        }
    </style>
</head>
<body>

<div class="modal_content">
    <h5>绑定用户信息!</h5>
    <div>
        <label for="phone1">注册手机号:</label><br/>
        <input id="phone1" type="text" name="phone" autocomplete="off" placeholder="请输入已绑定的手机号"/>
    </div>
    <div>
        <label for="code1">验证码:</label>
        <div class="code1">
            <input id="code1" type="text" autocomplete="off" placeholder="短信验证码"/>
            <input id="btnSendCode1" type="button" name="code" class="btn btn-default" value="获取验证码"
                   onClick="sendMessage1()"/>

        </div>
        <span id="msg" style="color: red"></span>
    </div>
    <div class="next">
        <button class="btn btn-default" id="btn" onclick="binding()">确定</button>
        <input id="btn1" type="hidden">
    </div>
</div>
<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"
        th:src="@{http://www.jq22.com/jquery/jquery-1.10.2.js}"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"
        th:src="@{https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js}"></script>

<script>
    var phoneReg = /(^1[3|4|5|7|8]\d{9}$)|(^09\d{8}$)/;//手机号正则
    var count = 20; //间隔函数,1秒执行
    var InterValObj1; //timer变量,控制时间
    var curCount1;//当前剩余秒数
    /*第一*/
    function sendMessage1() {
        curCount1 = count;
        var phone = $.trim($('#phone1').val());
        if (!phoneReg.test(phone)) {
            alert(" 请输入有效的手机号码");
            return false;
        }
        //设置button效果,开始计时
        $("#btnSendCode1").attr("disabled", "true");
        $("#btnSendCode1").val(+curCount1 + "秒再获取");
        InterValObj1 = window.setInterval(SetRemainTime1, 1000); //启动计时器,1秒执行一次
        //向后台发送处理数据

        $.ajax({
            url: "/getCode",
            data: {"phone": phone},
            dataType: "json",
            type: "get",
            success: function (d) {
                console.log("code", d.code);
                var num = d.count;
                if (num > 0 && num <= 5) {
                    alert("验证码:" + d.code);
                }
                if (num > 5) {
                    $("#msg").text("您操作异常!");
                    $("#btnSendCode1").attr("disabled", "true");
                    alert("由于您频繁操作请过一会在试!")
                }
            }
        });


    }

    function SetRemainTime1() {
        if (curCount1 == 0) {
            window.clearInterval(InterValObj1);//停止计时器
            $("#btnSendCode1").removeAttr("disabled");//启用按钮
            $("#btnSendCode1").val("重新发送");
        } else {
            curCount1--;
            $("#btnSendCode1").val(+curCount1 + "秒再获取");
        }
    }

    /*提交*/
    function binding() {

        var time = 60; //间隔函数,1秒执行

        var phone = $.trim($('#phone1').val());
        var code = $.trim($('#code1').val());
        var num = 0;
        var verificationCount = 0;
        var InterValObj2; //timer变量,控制时间

        $.ajax({
            url: "verificationCode",
            data: {"phone": phone, "code": code},
            dataType: "json",
            success: function (d) {
                verificationCount = d.verificationCount;
                if (d.status == 1) {
                    alert("注册成功!");
                    window.location = "login";
                } else {
                    $("#msg").text("验证码错误请重试");
                    num++;
                }
                if (verificationCount > 3 || num > 3) {
                    //设置button效果,开始计时
                    $("#btn").attr("disabled", "true");
                    $("#btn1").attr("type", "submit");
                    $("#btn1").val(+time + "秒再注册");
                    InterValObj2 = window.setInterval(SetRemainTime2, 1000); //启动计时器,1秒执行一次
                    $("#msg").text("由于您过多的验证输入错误,请1分钟过后再试!");
                }

                function SetRemainTime2() {
                    if (time == 0) {
                        window.clearInterval(InterValObj2);//停止计时器
                        $("#btn").removeAttr("disabled");//启用按钮
                        $("#btn1").attr("type", "hidden");
                    } else {
                        time--;
                        $("#btn1").val(+time + "秒再注册");
                    }
                }
            }
        });

    }
</script>
</body>
</html>

限制登录功能

需求描述:

用户在2分钟内,仅允许输入错误密码5次。
如果超过次数,限制其登录1小时。(要求每登录失败时,都要给相应提式)

代码参考:

代码实现
service层实现

package com.han.service.impl;

import com.han.entity.Login;
import com.han.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<String,Object> string;

    /**
     * 判断用户是否存在
     * @return
     */
    public Integer getString(String userName){

        String key = "userName:"+userName;
        if (redisTemplate.hasKey(key)){
            return 1;
        }else {
            return 0;
        }
    }

    /**
     * 判断用户登录
     * 成功返回1
     * 失败返回0
     * @param userName
     * @param password
     * @return
     */
    public Login loginVerification(String userName,String password){
        String key = "userName:"+userName;
        //用来记录登录的次数
        String countKey = "count:"+userName;
        //获取用户的密码
        String strPassword = (String) string.get(key);

        Login login = new Login();
        //如果userName的这个key存在就被限制了
        if (redisTemplate.hasKey(userName)){
            long time = string.getOperations().getExpire(userName, TimeUnit.MINUTES);
            login.setTime((int) time);
            return login;
        }else {

            //如果不存在设置登录次数默认为5
            if (!redisTemplate.hasKey(countKey)){
                redisTemplate.opsForValue().set(countKey,"5");
            }

            if (strPassword.equals(password)) {
                //如果登录成功就清除计数器
                redisTemplate.delete(countKey);
                login.setStatus(1);
            } else {
                //如果失败次数加一
                redisTemplate.opsForValue().decrement(countKey);
                int count = (int) string.get(countKey);
                if (count <= 0) {

                    string.set(userName, null, 1, TimeUnit.HOURS);
                    redisTemplate.delete(countKey);

                }
                login.setStatus(0);
                login.setCount(count);
            }
        }

        return login;
    }


    /**
     * 添加用户
     * @param userName
     * @param password
     * @return
     */
    public String setString(String userName,String password){
        String key = "userName:"+userName;
        string.set(key,password);
        return "用户名为:"+userName+"---->密码为:"+password;
    }
}


entity层实现

package com.han.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class Login implements Serializable {

    private Integer status;

    private Integer count;

    private Integer time;

}

config层配置

package com.han.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
        RedisTemplate<String,Object> template = new RedisTemplate <>();
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式。
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}


controller层实现

package com.han.controller;

import com.han.entity.Login;
import com.han.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class LoginController {

    @Autowired
    private LoginService loginService;

    @ResponseBody
    @RequestMapping(value = "getUserName")
    public String getUserName(String userName){
       Integer num = loginService.getString(userName);
        return Integer.toString(num);
    }

    @ResponseBody
    @RequestMapping(value = "loginVerification")
    public Login loginVerification(String userName,String password){
        Login login = loginService.loginVerification(userName, password);
        return login;
    }


}

页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta http-equiv="Pragma" content="no-cache">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
    <meta name="format-detection" content="telephone=yes"/>
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    <title>绑定信息</title>
    <!-- Bootstrap core CSS-->
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          th:href="@{https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css}">
    <style type="text/css">
        body {
            margin: 0;
            padding: 0;
        }

        .modal_content {
            padding: 30px;
            display: flex;
            justify-content: center;
            flex-direction: column;
        }

        .modal_content > div {
            margin-bottom: 20px;
        }

        .modal_content > h5:first-child {
            margin: 30px 0px;
        }

        #dialog label {
            color: #666;
        }

        #phone1,#userName ,#password{
            display: block;
            width: 100%;
            height: 70px;
            background: none;
            padding-top: 30px;
            border: 0;
            outline: none;
            text-align: center;
            margin-top: -30px;
            font-size: 16px;
            border-bottom: 1px solid rgba(0, 0, 0, .2);
            border-radius: 0;
        }

        .code1 {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            width: 100%;
            height: 70px;
            background: none;
            padding-top: 30px;
            margin-top: -30px;
            font-size: 16px;
            border-bottom: 1px solid rgba(0, 0, 0, .2);
            border-radius: 0;
        }

        #code1 {
            width: calc(100% - 90px);
            height: 55px;
            background: none;
            padding-top: 20px;
            border: 0;
            outline: none;
            text-align: center;
            margin-top: -20px;
            font-size: 16px;
        }

        #btnSendCode1 {
            width: 90px;
            height: 30px;
            padding: 0 5px;
            margin: 0;
            font-size: 14px;
            text-align: center;
            background: transparent;
            border-radius: 30px;
            color: #a07941;
            border-color: #a07941;

        }

        ::-webkit-input-placeholder { /* WebKit browsers */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        :-moz-placeholder { /* Mozilla Firefox 4 to 18 */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        ::-moz-placeholder { /* Mozilla Firefox 19+ */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        :-ms-input-placeholder { /* Internet Explorer 10+ */
            font-size: 14px;
            color: rgba(0, 0, 0, .4);
        }

        .next {
            text-align: center;
            margin: 20px 0;
        }

        .next button {
            width: 100%;
            height: 45px;
            padding: 0;
            margin: 0;
            background: #007BFF;
            color: #fff;
            border: 0;
            outline: none;
            border-radius: 3px;
        }
    </style>
</head>
<body>

<div class="modal_content">
    <h5>用户登录!</h5>
    <div>
        <label for="userName">用户名:</label><br/>
        <input id="userName" type="text" name="userName" autocomplete="off" placeholder="请输入用户名" onchange="sendMessage1()"/>
        <span id="msg" style="color: red"></span>
    </div>
    <div>
        <label for="password">密码:</label>
        <div class="code1">
            <input id="password" type="password" name="password" autocomplete="off" placeholder="用户密码"/>
        </div>
    </div>
    <div class="next">
        <button class="btn btn-default" id="btn" onclick="binding()">确定</button>
    </div>
</div>
<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"
        th:src="@{http://www.jq22.com/jquery/jquery-1.10.2.js}"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"
        th:src="@{https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js}"></script>

<script>

    /*第一*/
    function sendMessage1() {
        var userName = $.trim($('#userName').val());

        $.ajax({
            url: "/getUserName",
            data: {"userName": userName},
            dataType: "json",
            type: "get",
            success: function (d) {
                var num = d;
                if (num ==0) {
                    $("#msg").text("用户名不存在");
                    $("#btn").attr("disabled", "true");
                }else {
                    $("#msg").text("");
                    $("#btn").removeAttr("disabled");//启用按钮
                }
            }
        });

    }

    /*提交*/
    function binding() {

        var userName = $.trim($('#userName').val());
        var password = $.trim($('#password').val());
        $.ajax({
            url: "loginVerification",
            data: {"userName": userName, "password": password},
            dataType: "json",
            success: function (d) {
                var count = d.count;
                var time = d.time;
                if (d.status == 1) {
                    alert("登录成功!");
                    window.location = "home";
                } else {
                    if (time==null){
                        alert("密码错误,还剩余"+count+"次机会");
                    }else{
                        alert("您还要"+time+"分钟才能再次登录");
                    }
                }
            }
        });

    }
</script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值