1分钟学会SpringBoot2知识点,让你35岁不再失业(四)

第十五节、springboot2整合redis

1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

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

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-configuration-processor -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>



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

        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、application.yaml

#jedisPool配置开始
# 连接池中的最大空闲连接
redis:
  maxIdle: 30
  minIdle: 1
  maxTotal: 100
  maxWait: 10000
  host: 172.0.0.1
  port: 6379
  timeout: 10000
  password: 123456




3、RedisPoolConfig.java

package com.example.redis.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author 刘阳洋
 * @date 2020/4/29 14:42
 */
@ConfigurationProperties(prefix = "redis")
@Component
public class RedisPoolConfig {
    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private int port;
    @Value("${redis.maxTotal}")
    private int maxTotal;
    @Value("${redis.maxIdle}")
    private int maxIdle;
    @Value("${redis.minIdle}")
    private int minIdle;
    @Value("${redis.timeout}")
    private int timeout;
    @Value("${redis.maxWait}")
    private long maxWait;
    @Value("${redis.password}")
    private String password;

  @Bean
  public JedisPool init(){
      JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
          //连接池阻塞最大等待时间
         jedisPoolConfig.setMaxWaitMillis(maxWait);
         //连接池最大空闲连接数
         jedisPoolConfig.setMaxIdle(maxIdle);
        //连接池最小空闲连接数
         jedisPoolConfig.setMinIdle(maxIdle);
         //连接池最大链接数
         jedisPoolConfig.setMaxTotal(maxTotal);
         //连接池最小链接数
          jedisPoolConfig.setMinIdle(minIdle);

      JedisPool jedisPool=new JedisPool(jedisPoolConfig,host,port,timeout,password,0);
      System.out.println("password"+password);
         //JedisPool jedisPool=new JedisPool(jedisPoolConfig,"127.0.0.1",6379,10000,"foobared",0);
         return jedisPool;
  }

}

4、RedisService.java

package com.example.redis.service;

/**
 * @author 刘阳洋
 * @date 2020/4/29 14:59
 */


import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
public class RedisService {

    @Resource
    private  JedisPool jedisPool;

        //***************************对 key 常用操作**************************
    /**
     * 判断key是否存在
     * @param key
     * @return       boolean true 存在 false 不存在
     * @throws
     */
    public boolean exists(String key){
        Jedis jedis = null;
        boolean result;
        try {
            jedis=jedisPool.getResource();
            result=jedis.exists(key);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 删除指定的key,也可以传入一个包含key的数组
     * @param keys
     * @return       java.lang.Long 返回删除成功的个数
     * @throws
     */
    public Long del(String... keys) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result= jedis.del(keys);
        }  finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * KEYS pattern 通配符模式匹配
     * 查找所有符合给定模式 pattern 的 key 。
     * KEYS * 匹配数据库中所有 key 。
     * KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
     * KEYS h*llo 匹配 hllo 和 heeeeello 等。
     * KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,
     * 你最好还是用 Redis 的集合结构(set)来代替。
     * @param pattern
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<String> keys(String pattern) {
        Jedis jedis = null;
        Set<String> result ;
        try {
            jedis = jedisPool.getResource();
            result = jedis.keys(pattern);
        }  finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 设置过期时间
     * @param key
     * @param seconds
     * @return       Long 1:表示设置成功,0:设置失败
     * @throws
     */
    public Long expire(String key,int seconds){
        Jedis jedis=null;
        Long result=0L;
        try {
            jedis=jedisPool.getResource();
            if(seconds>0){
                result=jedis.expire(key,seconds);
            }
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 移除给定 key 的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(永不过期的 key )
     * @param key
     * @return       java.lang.Long 当生存时间移除成功时,返回 1 .如果 key 不存在或 key 没有设置生存时间,返回 0
     * @throws
     */
    public Long persist(String key) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result=jedis.persist(key);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 以秒为单位,返回给定 key 的剩余生存时间
     * @param key
     * @return       java.lang.Long 当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1 。否则,以秒为单位,返回 key
     *      *         的剩余生存时间
     * @throws
     */
    public Long ttl(String key) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result=jedis.ttl(key);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    //**************String数据类型********************
    /**
     * 获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将抛出异常,
     * 因为GET命令只能用于获取string Value,如果该Key不存在,返回null
     * @param key
     * @return       成功返回value 失败返回null
     * @throws
     */
    public String get(String key) {
        Jedis jedis = null;
        String value ;
        try {
            jedis = jedisPool.getResource();
            value = jedis.get(key);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return value;
    }

    /**
     * 设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。总是返回"OK"。
     * @param key
     * @param value
     * @param expire 过期时间秒
     * @return       void
     * @throws
     */
    public String set(String key, String value,int expire) {

        Jedis jedis = null;
        String result;
        try {
            jedis = jedisPool.getResource();
            result=jedis.set(key,value);
            if(expire>0){
                jedis.expire(key, expire);
            }
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 加锁操作:jedis.set(key,value,"NX","EX",timeOut)【保证加锁的原子操作】
     * @param key key就是redis的key值作为锁的标识,
     * @param value  value在这里作为客户端的标识,
     * @param nxxx NX:只有这个key不存才的时候才会进行操作,if not exists;
     * @param nxxx XX:只有这个key存才的时候才会进行操作,if it already exist;
     * @param expx EX:设置key的过期时间为秒,具体时间由第5个参数决定
     * @param expx PX:设置key的过期时间为毫秒,具体时间由第5个参数决定
     * @param time 通过timeOut设置过期时间保证不会出现死锁【避免死锁】
     * @return       java.lang.String 成功OK不成功null
     * @throws
     */

    public String  set(String key, String value, String nxxx, String expx, long time){
        Jedis jedis=null;
        String result;
        try {
            jedis=jedisPool.getResource();
            result = jedis.set(key, value, nxxx, expx, time);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 将指定Key的Value原子性的递增1。如果该Key不存在,其初始值为0,在incr之后其值为1。
     * 如果Value的值不能转换为整型值,如Hi,该操作将执行失败并抛出相应的异常。
     * 注意:该操作的取值范围是64位有符号整型;返回递增后的Value值。
     * @param key
     * @return       java.lang.Long 加值后的结果
     * @throws
     */
    public Long incr(String key) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.incr(key);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 将指定Key的Value原子性的递增1。如果该Key不存在,其初始值为0,在decr之后其值为-1。
     * 如果Value的值不能转换为整型值,如Hi,该操作将执行失败并抛出相应的异常。
     * 注意:该操作的取值范围是64位有符号整型;返回递减后的Value值。
     * @Author:      小霍
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Long 加值后的结果
     * @throws
     */
    public Long decr(String key) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.decr(key);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    //******************hash数据类型*********************
    /**
     * 通过key 和 field 获取指定的 value
     * @param key
     * @param field
     * @return       java.lang.String
     * @throws
     */
    public String hget(String key, String field) {
        Jedis jedis = null;
        String result ;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hget(key, field);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以用于存储参数中的Field/Value对,
     * 如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。
     * 返回1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。
     * @param key
     * @param field
     * @param value
     * @return       java.lang.Long
     * @throws
     */
    public Long hset(String key, String field, String value) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hset(key, field, value);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 判断指定Key中的指定Field是否存在,返回true表示存在,false表示参数中的Field或Key不存在。
     * @param key
     * @param field
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean hexists(String key, String field) {

        Jedis jedis = null;
        Boolean result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hexists(key, field);
        }finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 从指定Key的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略,
     * 返回实际删除的Field数量。如果Key不存在,则将其视为空Hashes,并返回0。1
     * @param key
     * @param fields
     * @return       java.lang.Long
     * @throws
     */
    public Long hdel(String key, String... fields) {
        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hdel(key, fields);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    /**
     * 通过key获取所有的field和value
     * @param key
     * @return       java.util.Map<java.lang.String,java.lang.String>
     * @throws
     */
    public Map<String, String> hgetall(String key) {
        Jedis jedis = null;
        Map<String, String> result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hgetAll(key);
        } finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。
     * 如果Key不存在,则创建新Key,同时设定参数中的Field/Value。
     * @param key
     * @param hash
     * @return       java.lang.String
     * @throws
     */
    public String hmset(String key, Map<String, String> hash) {

        Jedis jedis = null;
        String result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hmset(key, hash);
        } finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    /**
     * 对应key的字段自增相应的值
     * @param key
     * @param field
     * @param increment
     * @return       java.lang.Long
     * @throws
     */
    public Long hIncrBy(String key,String field,long increment){

        Jedis jedis=null;
        Long result;
        try {
            jedis=jedisPool.getResource();
            return jedis.hincrBy(key, field, increment);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
    }
    //***************List数据类型***************
    /**
     * 向列表左边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部插入。
     * 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
     * @param key
     * @param strs 可以使一个string 也可以使string数组
     * @return       java.lang.Long 返回操作的value个数
     * @throws
     */
    public Long lpush(String key, String... strs) {

        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.lpush(key, strs);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 向列表右边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。
     * 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。1
     * @param key
     * @param strs 可以使一个string 也可以使string数组
     * @return       java.lang.Long 返回操作的value个数
     * @throws
     */
    public Long rpush(String key, String... strs) {

        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.rpush(key, strs);
        }finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 返回并弹出指定Key关联的链表中的第一个元素,即头部元素。如果该Key不存在,
     * 返回nil。LPOP命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。
     * @Author:      小霍
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.String
     * @throws
     */
    public String lpop(String key) {

        Jedis jedis = null;
        String result ;
        try {
            jedis = jedisPool.getResource();
            result = jedis.lpop(key);
        }finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 返回并弹出指定Key关联的链表中的最后一个元素,即头部元素。如果该Key不存在,返回nil。
     * RPOP命令执行两步操作:第一步是将列表右边的元素从列表中移除,第二步是返回被移除的元素值。0.1
     * @param key
     * @return       java.lang.String
     * @throws
     */
    public String rpop(String key) {

        Jedis jedis = null;
        String result ;
        try {
            jedis = jedisPool.getResource();
            result = jedis.rpop(key);
        } finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     *该命令的参数start和end都是0-based。即0表示链表头部(leftmost)的第一个元素。
     * 其中start的值也可以为负值,-1将表示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。
     * 该命令在获取元素时,start和end位置上的元素也会被取出。如果start的值大于链表中元素的数量,
     * 空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括start)开始,链表中剩余的所有元素。
     * 注:Redis的列表起始索引为0。显然,LRANGE numbers 0 -1 可以获取列表中的所有元素。返回指定范围内元素的列表。
     * @param key
     * @param start
     * @param end
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    public List<String> lrange(String key, long start, long end) {

        Jedis jedis = null;
        List<String> result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.lrange(key, start, end);
        }finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }



    /**
     * 该命令将返回链表中指定位置(index)的元素,index是0-based,表示从头部位置开始第index的元素,
     * 如果index为-1,表示尾部元素。如果与该Key关联的不是链表,该命令将返回相关的错误信息。 如果超出index返回这返回nil。
     * @param key
     * @param index
     * @return       java.lang.String
     * @throws
     */
    public String lindex(String key, long index) {

        Jedis jedis = null;
        String result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.lindex(key, index);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    //***************Set数据类型*************
    /**
     * 如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。
     * 如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。返回实际插入的成员数量。
     * @param key
     * @param members 可以是一个String 也可以是一个String数组
     * @return       java.lang.Long 添加成功的个数
     * @throws
     */
    public Long sadd(String key, String... members) {

        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.sadd(key, members);
        } finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }



    /**
     * 判断参数中指定成员是否已经存在于与Key相关联的Set集合中。返回1表示已经存在,0表示不存在,或该Key本身并不存在。
     * @param key
     * @param member
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean sismember(String key, String member) {

        Jedis jedis = null;
        Boolean result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.sismember(key, member);
        }  finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 通过key获取set中所有的value
     * @param key
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<String> smembers(String key) {

        Jedis jedis = null;
        Set<String> result ;
        try {
            jedis = jedisPool.getResource();
            result = jedis.smembers(key);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    //**********Sorted Set 数据类型********************
    /**
     *添加参数中指定的所有成员及其分数到指定key的Sorted Set中,在该命令中我们可以指定多组score/member作为参数。
     * 如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。
     * 如果键不存在,该命令将为该键创建一个新的Sorted Set Value,并将score/member对插入其中。
     * 如果该键已经存在,但是与其关联的Value不是Sorted Set类型,相关的错误信息将被返回。添加成功返回实际插入的成员数量。
     * @param key
     * @param score
     * @param member
     * @return       java.lang.Long
     * @throws
     */
    public Long zadd(String key, double score, String member) {

        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.zadd(key, score, member);
        } finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    /**
     * 返回Sorted Set中的成员数量,如果该Key不存在,返回0。
     * @param key
     * @return       java.lang.Long
     * @throws
     */
    public Long zcard(String key) {

        Jedis jedis = null;
        Long result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.zcard(key);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,
     * 此后再将其分数加上increment。如果Key不存在,该命令将创建该Key及其关联的Sorted Set,
     * 并包含参数指定的成员,其分数为increment参数。如果与该Key关联的不是Sorted Set类型,
     * 相关的错误信息将被返回。如果不报错则以串形式表示的新分数。
     * @param key
     * @param score
     * @param member
     * @return       java.lang.Double
     * @throws
     */
    public Double zincrby(String key, double score, String member) {
        Jedis jedis = null;
        Double result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.zincrby(key, score, member);
        }  finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    /**
     * 如果该成员存在,以字符串的形式返回其分数,否则返回null
     * @param key
     * @param member
     * @return       java.lang.Double
     * @throws
     */
    public Double zscore(String key, String member) {
        Jedis jedis = null;
        Double result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.zscore(key, member);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示最后一个成员。如果start大于该Sorted
     * Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索引值,
     * 该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,
     * 该命令在返回的结果中将包含每个成员的分数值,如value1,score1,value2,score2...。
     * @param key
     * @param min
     * @param max
     * @return       java.util.Set<java.lang.String> 指定区间内的有序集成员的列表。
     * @throws
     */
    public Set<String> zrange(String key, long start, long stop) {
        Jedis jedis = null;
        Set<String> result;
        try {
            jedis = jedisPool.getResource();
            result= jedis.zrange(key, start, stop);
        }  finally {
            if (jedis!=null){
                jedis.close();
            }
        }
        return result;
    }
    /**
     * 该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,
     * 即从高到低的顺序。如果成员具有相同的分数,则按降序字典顺序排序。
     * @param key
     * @param start
     * @param end
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<String> zrevrange(String key, long start, long end) {
        Jedis jedis = null;
        Set<String> result;
        try {
            jedis = jedisPool.getResource();
            result = jedis.zrevrange(key, start, end);
        }finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }


    /**
     * 该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。
     * 需要注意的是该命令中的min和max参数的顺序和ZRANGEBYSCORE命令是相反的。
     * @param key
     * @param max
     * @param min
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<String> zrevrangeByScore(String key, double max, double min) {
        Jedis jedis = null;
        Set<String> result ;
        try {
            jedis = jedisPool.getResource();
            result = jedis.zrevrangeByScore(key, max, min);
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return result;
    }

}

5、启动类

package com.example.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@SpringBootApplication
public class RedisApplication {
    public static void main(String[] args) {

        SpringApplication.run(RedisApplication.class, args);
        System.out.println("启动成功");

    }

    }



6、测试

package com.example.redis;

import com.example.redis.service.RedisService;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;


@RunWith(SpringRunner.class)
@SpringBootTest
class RedisApplicationTests {

    @Autowired
    private RedisService redisService;
    @Autowired
    private JedisPool jedisPool;
    @Test
    public void testRedis(){

//        String password="foobared";
//        String localhost="127.0.0.1";
//        int port=6379;
//        Jedis jedis=new Jedis(localhost,port);
//        jedis.auth(password);
//        System.out.println(jedis.get("name"));

        System.out.println("结果为:"+jedisPool.getResource());
        System.out.println("结果为:"+redisService.get("name"));


    }
}

第十六节、redis分布式session共享

1、以往我们的项目都是部署在单台服务器运行,因为客户的所有请求都是由唯一服务器来处理,sessionId 保存在这台服务器上是没有问
题的。但是当项目同时部署在多台服务器上时,就会出现 sessionId 共享问题。
现在有两台服务器同时运行,分别是 Server A 和 Server B,通过 nginx 配置了负载均衡,客户端的请求会被随机分配到两台服务器上
进行处理。假设客户现在有第一次请求(登录请求)被分配到 Server A 进行处理,Server A 接受到请求之后会生成 sessionId 并且保
存到内存当中,然后返回给客户(浏览器),浏览器会把 sessionId 保存到 cookie 中,第一次请求完成。如果之后每一次请求还是由
Server A 来处理当然一切正常,但是一旦出现意外(比如 Server A 宕机)或者nginx 采用了轮询、weight方式负载均衡,请求被分配
到 Server B进行处理,这时候 Server B 拿到客户请求的 sessionId 是由 Server A 生成的,两边对不上啊 ! 于是客户会发现,本来还
用的好好的,怎么会突然跳到登录页需要重新登录了呢!!!???
那我们该怎么去解决呢?
既然问题的根源出在 sessionId 无法共享上面,那么是不是让 sessionId 可以在多台服务器之间共享就可以了?换个思路,即把
sessionId 保存到数据库即可(最终选择redis,因为会比较快!),验证时不再从当前服务器获取 sessionId 改从 redis 中获取即可!

实现思路:

  1. 登录页面提交用户名密码。
  2. 登录成功后生成token。Token相当于原来的sessionid,字符串,可以使用uuid。
  3. 把用户信息保存到redis。Key就是token,value就是userId。
  4. 设置key的过期时间。模拟Session的过期时间。一般一个小时。
  5. 拦截器拦截请求校验 sessionId。

2、登录成功 生成 sessionId 存入 redis

   /**
     * 用户登录
     * @param vo
     * @return
     */

    @Override
    public LoginRespVO login(LoginReqVO vo) {

        SysUser sysUser = sysUserDao.selectByUsername(vo.getUsername());
        if (sysUser == null) {
            throw new BusinessException(3001, "不存在该用户,请先注册");

        }

        if (sysUser.getStatus() == 2) {
            throw new BusinessException(3002, "该账号已被禁用请联系系统管理员");
        }
        if (!PasswordUtils.matches(sysUser.getSalt(), vo.getPassword(), sysUser.getPassword())) {
            throw new BusinessException(3003, "用户名密码不匹配");
        }

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

        LoginRespVO loginRespVO = new LoginRespVO();
        loginRespVO.setUserId(sysUser.getId());
        loginRespVO.setToken(token);

        //分布式session凭证存入redis 60分钟失效
        redisService.set(token,sysUser.getId(),60, TimeUnit.MINUTES);
  
        return  loginRespVO;


    }

3、SessionInterceptor 拦截器校验 sessionId

package com.example.distributed.demo.interceptor;

import com.example.distributed.demo.exception.BusinessException;
import com.example.distributed.demo.service.impl.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:52
 */

public class TokenInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token=request.getHeader("token");
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(3004,"用户凭证不能为空,请重新登录");
        }else {
            if(!redisService.hasKey(token)){
                throw new BusinessException(3005,"用户凭证无效,请重新登录");
            }
            String userId= (String) redisService.get(token);
            if(redisService.hasKey(userId)&&!token.equals(redisService.get(userId))){
                throw new BusinessException(3006,"您的账号已经在异地登录,请重新登录");
            }
        }
        return true;
    }

}

4、配置拦截器策略

package com.example.distributed.demo.config;

import com.example.distributed.demo.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:51
 */
//配置拦截器策略
@Configuration
public class WebAppConfig implements WebMvcConfigurer {

    @Bean
    public TokenInterceptor tokenInterceptor(){
        return new TokenInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/user/login","/api/user/register","/api/user/code/*");
    }
}

第十七节、redis 异地登录提醒下线

1、业务分析
最近接到产品提的一个需求说,一个账号同时只能在一个地方登录,如果在其他地方登录则提示已在别处登录,同时,同一浏览器同
时只能登录一个用户。
思路:

  1. 登录页面提交用户名密码。
  2. 登录成功后生成token。Token相当于原来的 sessionid,字符串,可以使用uuid。
  3. 把用户信息保存到redis。Key就是token,value就是userId。
  4. 设置key的过期时间。模拟Session的过期时间。一般一个小时。
  5. 标记 Token把Token存入redis,key为 userId,value 就是 Token过期时间和 key 为 Token 的过期时间一致
  6. 拦截器拦截请求校验 token。
  7. 获取 userId 后再去比较 header 携带的token和redis标记的token是否一致,不一致则提示用户已经异地登录。
    2、代码实现
    /**
     * 用户登录
     * @param vo
     * @return
     */

    @Override
    public LoginRespVO login(LoginReqVO vo) {

        SysUser sysUser = sysUserDao.selectByUsername(vo.getUsername());
        if (sysUser == null) {
            throw new BusinessException(3001, "不存在该用户,请先注册");

        }

        if (sysUser.getStatus() == 2) {
            throw new BusinessException(3002, "该账号已被禁用请联系系统管理员");
        }
        if (!PasswordUtils.matches(sysUser.getSalt(), vo.getPassword(), sysUser.getPassword())) {
            throw new BusinessException(3003, "用户名密码不匹配");
        }

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

        LoginRespVO loginRespVO = new LoginRespVO();
        loginRespVO.setUserId(sysUser.getId());
        loginRespVO.setToken(token);

        //异地登录提醒下线功能
        redisService.set(sysUser.getId(),token,60,TimeUnit.MINUTES);

        return  loginRespVO;
        
    }

3、修改token拦截器逻辑

package com.example.distributed.demo.interceptor;

import com.example.distributed.demo.exception.BusinessException;
import com.example.distributed.demo.service.impl.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:52
 */

public class TokenInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token=request.getHeader("token");
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(3004,"用户凭证不能为空,请重新登录");
        }else {
            if(!redisService.hasKey(token)){
                throw new BusinessException(3005,"用户凭证无效,请重新登录");
            }
            String userId= (String) redisService.get(token);
            if(redisService.hasKey(userId)&&!token.equals(redisService.get(userId))){
                throw new BusinessException(3006,"您的账号已经在异地登录,请重新登录");
            }
        }
        return true;
    }

}

第十八节、redis 注册短信验证码

1、短信验证码是所有项目必不可少的基础功能模块之一,假如突然有一天你领导给你布置的一个需求。在用户注册的时候要校验手机
号。
要求如下:

  1. 注册的时候校验手机号
  2. 每个手机号每天最多发送五条注册短信验证码
  3. 验证码5分钟内有效。
    思路:
  4. 发送前验证手机号是否符合要求。
  5. 生成短信验证码。
  6. 发送验证码到手机。
  7. 把验证码存入redis
  8. 标记手机号
  9. 注册的时候校验手机号和验证码是否正确

第十九节、 redis 计数器(订单号/特殊有规律编码/点赞数)

1、在现实开发中,经常遇到数据组或者产品给的需求,比如统计某个功能 pv、uv 数量、文章点赞数、或着有规律的编码等。
需求:
生成订单号为20191020D0000001 一天内可以生成9999999个不重复订单号(这个由我们自己设定)

实现思路:

  1. 特定常量+当前日期作为key(确保唯一)
  2. 每新生成一个订单号 incr 自增
  3. 编码=日期+类型+自增部分

第二十节、 redis 购物车

1、在做购物车的时候要我们要考虑到对于一个客户来说 不同规格,不同商品 ,在实际应该中我们该怎么处理呢?
需求:

  1. 加入购物车先得登录。
  2. 记录用户购物车数据。
    思路:
    我们在做购物车功能的时候,我们可以用 redis hash类型来做。首先前端提交购物车的时候需要先判断是否登录,需要把商品的
    skuId、规格属性specificationIds (多个的id以‘,’拼接)、和商品的数量。展示的时候去redis 拿到对应购物车数据查询最新数据
    redis hash 格式为
    在这里插入图片描述

第二十一节、 redis templete 和以上功能的代码实现

1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example.distributed</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>


        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.8.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>



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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

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

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、建表语句

CREATE TABLE `sys_user` (
`id` varchar(64) NOT NULL  COMMENT '用户id' ,
`username` varchar(64) NOT NULL COMMENT '账户名称',
`salt` varchar(20) DEFAULT NULL COMMENT '加密盐值',
`password` varchar(200) NOT NULL COMMENT '用户密码密文',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号码',
`dept_id` varchar(64) DEFAULT NULL COMMENT '部门id',
`real_name` varchar(64) DEFAULT NULL COMMENT '真实姓名',
`nick_name` varchar(64) DEFAULT NULL COMMENT '昵称',
`email` varchar(64) DEFAULT NULL COMMENT '邮箱',
`status` tinyint(4) DEFAULT '1' COMMENT '账户状态1正常2锁定',
`sex` tinyint(4) DEFAULT NULL COMMENT '性别1男2女',
`deleted` tinyint(4) DEFAULT '0'  COMMENT '0未删除 1已删除',
`create_id` varchar(64) DEFAULT NULL COMMENT '创建人',
`update_id` varchar(64) DEFAULT NULL COMMENT '更新人',
`create_where` varchar(64) DEFAULT NULL COMMENT '创建来源1web 2android 3ios ',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT '1970-01-01 08:00:01',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3、application.yaml


#File-->Settings-->File Encodings
#reids连接池配置lettuce
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    timeout: PT10S
    lettuce:
      pool:
        max-active: 100  #连接池最大连接数(使用负值表示没有限制) 默认 8
        max-idle: 30     # 连接池中的最大空闲连接 默认 8
        min-idle: 1      # 连接池中的最小空闲连接 默认 0
        max-wait: PT10S  #连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1

#Druid数据库连接池配置
#1、监控数据库访问性能 2、详细统计线上的访问性能 3、SQL执行日志 4、扩展JDBC

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss #如果使用字符串表示,用这行设置格式
    timezone: GMT+8
    serialization:
      write-dates-as-timestamps: false #使用时间戳,使用数值timestamp表示日期  serverTimezone=GMT%2B8
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/springboot2?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 123456
###############    连接池配置   ################
#连接池建立时创建的初始化连接数
      initial-size: 5
#最大活跃连接数
      max-active: 20
#最小活跃连接数
      min-idle: 5
#配置获取连接等待超时时间
      max-wait: 60000
#打开PSCache,并且指定每个连接上PSCache的大小
      max-pool-prepared-statement-per-connection-size: 20
      validation-query: SELECT 1 FROM DUAL
      query-timeout: 30000
  #是否获得连接放回连接池后检测其可用性
      test-on-borrow: false
#是否在连接放回连接池后检测其可用性
      test-on-return: false
#是否在连接空闲一段时间后检测其可用性
      test-while-idle: true
  #配置间隔多久进行一次检测,检测需要关闭空闲连接单位是毫秒
      time-between-eviction-runs-millis: 60000
   #配置一个连接在池中最小生存时间单位是毫秒
      min-evictable-idle-time-millis: 300000
 #监控后台账号和密码
      stat-view-servlet:
        login-username: admin
        login-password: 123456
#监控页面  http://localhost:8888/druid


#mybatis配置
mybatis:
  mapper-locations: classpath:generator/*.xml
server:
  port: 8889



4、mybatis配置
mapper:

package com.example.distributed.demo.mapper;


import com.example.distributed.demo.entity.SysUser;
import org.springframework.stereotype.Repository;

@Repository
public interface SysUserDao {
    int deleteByPrimaryKey(String id);

    int insert(SysUser record);

    int insertSelective(SysUser record);

    SysUser selectByPrimaryKey(String id);

    int updateByPrimaryKeySelective(SysUser record);

    int updateByPrimaryKey(SysUser record);

    SysUser selectByUsername(String username);
}

entity:

package com.example.distributed.demo.entity;

import org.springframework.boot.autoconfigure.domain.EntityScan;

import java.io.Serializable;
import java.util.Date;

/**
 * sys_user
 * @author 
 */

public class SysUser implements Serializable {
    /**
     * id
     */
    private String id;

    private String username;

    private String salt;

    private String password;

    private String phone;

    /**
     * id
     */
    private String deptId;

    private String realName;

    private String nickName;

    private String email;

    /**
     * 12
     */
    private Byte status;

    /**
     * 12
     */
    private Byte sex;

    /**
     * 0 1
     */
    private Byte deleted;

    private String createId;

    private String updateId;

    /**
     * 1web 2android 3ios 
     */
    private String createWhere;

    private Date createTime;

    private Date updateTime;

    private static final long serialVersionUID = 1L;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getDeptId() {
        return deptId;
    }

    public void setDeptId(String deptId) {
        this.deptId = deptId;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Byte getStatus() {
        return status;
    }

    public void setStatus(Byte status) {
        this.status = status;
    }

    public Byte getSex() {
        return sex;
    }

    public void setSex(Byte sex) {
        this.sex = sex;
    }

    public Byte getDeleted() {
        return deleted;
    }

    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    public String getCreateId() {
        return createId;
    }

    public void setCreateId(String createId) {
        this.createId = createId;
    }

    public String getUpdateId() {
        return updateId;
    }

    public void setUpdateId(String updateId) {
        this.updateId = updateId;
    }

    public String getCreateWhere() {
        return createWhere;
    }

    public void setCreateWhere(String createWhere) {
        this.createWhere = createWhere;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.distributed.demo.mapper.SysUserDao">
  <resultMap id="BaseResultMap" type="com.example.distributed.demo.entity.SysUser">
    <id column="id" jdbcType="VARCHAR" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="salt" jdbcType="VARCHAR" property="salt" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="phone" jdbcType="VARCHAR" property="phone" />
    <result column="dept_id" jdbcType="VARCHAR" property="deptId" />
    <result column="real_name" jdbcType="VARCHAR" property="realName" />
    <result column="nick_name" jdbcType="VARCHAR" property="nickName" />
    <result column="email" jdbcType="VARCHAR" property="email" />
    <result column="status" jdbcType="TINYINT" property="status" />
    <result column="sex" jdbcType="TINYINT" property="sex" />
    <result column="deleted" jdbcType="TINYINT" property="deleted" />
    <result column="create_id" jdbcType="VARCHAR" property="createId" />
    <result column="update_id" jdbcType="VARCHAR" property="updateId" />
    <result column="create_where" jdbcType="VARCHAR" property="createWhere" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
  </resultMap>
  <sql id="Base_Column_List">
    id, username, salt, `password`, phone, dept_id, real_name, nick_name, email, `status`, 
    sex, deleted, create_id, update_id, create_where, create_time, update_time
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from sys_user
    where id = #{id,jdbcType=VARCHAR}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.String">
    delete from sys_user
    where id = #{id,jdbcType=VARCHAR}
  </delete>
  <insert id="insert"  parameterType="com.example.distributed.demo.entity.SysUser" >
    insert into sys_user (id,username, salt, `password`,
      phone, dept_id, real_name, 
      nick_name, email, `status`, 
      sex, deleted, create_id, 
      update_id, create_where, create_time, 
      update_time)
    values (#{id,jdbcType=VARCHAR},#{username,jdbcType=VARCHAR}, #{salt,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
      #{phone,jdbcType=VARCHAR}, #{deptId,jdbcType=VARCHAR}, #{realName,jdbcType=VARCHAR}, 
      #{nickName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{status,jdbcType=TINYINT}, 
      #{sex,jdbcType=TINYINT}, #{deleted,jdbcType=TINYINT}, #{createId,jdbcType=VARCHAR}, 
      #{updateId,jdbcType=VARCHAR}, #{createWhere,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, 
      #{updateTime,jdbcType=TIMESTAMP})
  </insert>
  <insert id="insertSelective"  parameterType="com.example.distributed.demo.entity.SysUser" >
    insert into sys_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="username != null">
        username,
      </if>
      <if test="salt != null">
        salt,
      </if>
      <if test="password != null">
        `password`,
      </if>
      <if test="phone != null">
        phone,
      </if>
      <if test="deptId != null">
        dept_id,
      </if>
      <if test="realName != null">
        real_name,
      </if>
      <if test="nickName != null">
        nick_name,
      </if>
      <if test="email != null">
        email,
      </if>
      <if test="status != null">
        `status`,
      </if>
      <if test="sex != null">
        sex,
      </if>
      <if test="deleted != null">
        deleted,
      </if>
      <if test="createId != null">
        create_id,
      </if>
      <if test="updateId != null">
        update_id,
      </if>
      <if test="createWhere != null">
        create_where,
      </if>
      <if test="createTime != null">
        create_time,
      </if>
      <if test="updateTime != null">
        update_time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=VARCHAR},
      </if>
      <if test="username != null">
        #{username,jdbcType=VARCHAR},
      </if>
      <if test="salt != null">
        #{salt,jdbcType=VARCHAR},
      </if>
      <if test="password != null">
        #{password,jdbcType=VARCHAR},
      </if>
      <if test="phone != null">
        #{phone,jdbcType=VARCHAR},
      </if>
      <if test="deptId != null">
        #{deptId,jdbcType=VARCHAR},
      </if>
      <if test="realName != null">
        #{realName,jdbcType=VARCHAR},
      </if>
      <if test="nickName != null">
        #{nickName,jdbcType=VARCHAR},
      </if>
      <if test="email != null">
        #{email,jdbcType=VARCHAR},
      </if>
      <if test="status != null">
        #{status,jdbcType=TINYINT},
      </if>
      <if test="sex != null">
        #{sex,jdbcType=TINYINT},
      </if>
      <if test="deleted != null">
        #{deleted,jdbcType=TINYINT},
      </if>
      <if test="createId != null">
        #{createId,jdbcType=VARCHAR},
      </if>
      <if test="updateId != null">
        #{updateId,jdbcType=VARCHAR},
      </if>
      <if test="createWhere != null">
        #{createWhere,jdbcType=VARCHAR},
      </if>
      <if test="createTime != null">
        #{createTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updateTime != null">
        #{updateTime,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.example.distributed.demo.entity.SysUser">
    update sys_user
    <set>
      <if test="username != null">
        username = #{username,jdbcType=VARCHAR},
      </if>
      <if test="salt != null">
        salt = #{salt,jdbcType=VARCHAR},
      </if>
      <if test="password != null">
        `password` = #{password,jdbcType=VARCHAR},
      </if>
      <if test="phone != null">
        phone = #{phone,jdbcType=VARCHAR},
      </if>
      <if test="deptId != null">
        dept_id = #{deptId,jdbcType=VARCHAR},
      </if>
      <if test="realName != null">
        real_name = #{realName,jdbcType=VARCHAR},
      </if>
      <if test="nickName != null">
        nick_name = #{nickName,jdbcType=VARCHAR},
      </if>
      <if test="email != null">
        email = #{email,jdbcType=VARCHAR},
      </if>
      <if test="status != null">
        `status` = #{status,jdbcType=TINYINT},
      </if>
      <if test="sex != null">
        sex = #{sex,jdbcType=TINYINT},
      </if>
      <if test="deleted != null">
        deleted = #{deleted,jdbcType=TINYINT},
      </if>
      <if test="createId != null">
        create_id = #{createId,jdbcType=VARCHAR},
      </if>
      <if test="updateId != null">
        update_id = #{updateId,jdbcType=VARCHAR},
      </if>
      <if test="createWhere != null">
        create_where = #{createWhere,jdbcType=VARCHAR},
      </if>
      <if test="createTime != null">
        create_time = #{createTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updateTime != null">
        update_time = #{updateTime,jdbcType=TIMESTAMP},
      </if>
    </set>
    where id = #{id,jdbcType=VARCHAR}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.example.distributed.demo.entity.SysUser">
    update sys_user
    set username = #{username,jdbcType=VARCHAR},
      salt = #{salt,jdbcType=VARCHAR},
      `password` = #{password,jdbcType=VARCHAR},
      phone = #{phone,jdbcType=VARCHAR},
      dept_id = #{deptId,jdbcType=VARCHAR},
      real_name = #{realName,jdbcType=VARCHAR},
      nick_name = #{nickName,jdbcType=VARCHAR},
      email = #{email,jdbcType=VARCHAR},
      `status` = #{status,jdbcType=TINYINT},
      sex = #{sex,jdbcType=TINYINT},
      deleted = #{deleted,jdbcType=TINYINT},
      create_id = #{createId,jdbcType=VARCHAR},
      update_id = #{updateId,jdbcType=VARCHAR},
      create_where = #{createWhere,jdbcType=VARCHAR},
      create_time = #{createTime,jdbcType=TIMESTAMP},
      update_time = #{updateTime,jdbcType=TIMESTAMP}
    where id = #{id,jdbcType=VARCHAR}
  </update>

  <select id="selectByUsername" resultMap="BaseResultMap">
    select <include refid="Base_Column_List"></include>
    from sys_user
    where username=#{username} and deleted=0
  </select>
</mapper>

5、config包
RedisConfig:

package com.example.distributed.demo.config;

import com.example.distributed.demo.serializer.MyStringRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:01
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new MyStringRedisSerializer());
        redisTemplate.setHashValueSerializer(new MyStringRedisSerializer());
        return redisTemplate;
    }
}

redis实现类:

package com.example.distributed.demo.service.impl;

import com.example.distributed.demo.exception.BusinessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:05
 */

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;


    /** -------------------key相关操作--------------------- */

    /**
     * 是否存在key
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean hasKey(String key) {
        if (null==key){
            return false;
        }
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除key
     * @Author:     刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       Boolean  成功返回true 失败返回false
     * @throws
     */
    public Boolean delete(String key) {
        if (null==key){
            return false;
        }
        return redisTemplate.delete(key);
    }

    /**
     * 批量删除key
     * @Author:      刘阳洋
     * @CreateDate:  2019/8/27 20:27
     * @UpdateUser:
     * @UpdateDate:  2019/8/27 20:27
     * @Version:     0.0.1
     * @param keys
     * @return       Long 返回成功删除key的数量
     * @throws
     */
    public Long delete(Collection<String> keys) {
        return redisTemplate.delete(keys);
    }


    /**
     * 设置过期时间
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param timeout
     * @param unit
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        if (null==key||null==unit){
            return false;
        }
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 查找匹配的key
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param pattern
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<String> keys(String pattern) {
        if (null==pattern){
            return null;
        }
        return redisTemplate.keys(pattern);
    }


    /**
     * 移除 key 的过期时间,key 将持久保持
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean persist(String key) {
        if (null==key){
            return false;
        }
        return redisTemplate.persist(key);
    }

    /**
     * 返回 key 的剩余的过期时间
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param unit
     * @return       java.lang.Long 当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1 。否则,以秒为单位,返回 key的剩余生存时间
     * @throws
     */
    public Long getExpire(String key, TimeUnit unit) {
        if(null==key||null==unit){
            throw new BusinessException(4001004,"key or TomeUnit 不能为空");
        }
        return redisTemplate.getExpire(key, unit);
    }

    //*************String相关数据类型***************************
    /**
     * 设置指定 key 的值
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param value
     * @return       void
     * @throws
     */
    public void set(String key, Object value) {

        if(null==key||null==value){
            return;
        }
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 设置key 的值 并设置过期时间
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param value
     * @param time
     * @param unit
     * @return       void
     * @throws
     */
    public void set(String key,Object value,long time,TimeUnit unit){

        if(null==key||null==value||null==unit){
            return;
        }
        redisTemplate.opsForValue().set(key,value,time,unit);
    }
    /**
     * 设置key 的值 并设置过期时间
     * key存在 不做操作返回false
     * key不存在设置值返回true
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param value
     * @param time
     * @param unit
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean setifAbsen(String key,Object value,long time,TimeUnit unit){

        if(null==key||null==value||null==unit){
            throw new BusinessException(4001004,"kkey、value、unit都不能为空");
        }
        return redisTemplate.opsForValue().setIfAbsent(key,value,time,unit);
    }
    /**
     * 获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将抛出异常,
     * 因为GET命令只能用于获取string Value,如果该Key不存在,返回null
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Object
     * @throws
     */
    public Object get(String key){

        if(null==key){
            return null;
        }
        return  redisTemplate.opsForValue().get(key);
    }
    /**
     * 很明显先get再set就说先获取key值对应的value然后再set 新的value 值。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param value
     * @return       java.lang.Object
     * @throws
     */
    public Object getSet(String key,Object value){

        if(null==key){
            return null;
        }
        return redisTemplate.opsForValue().getAndSet(key,value);
    }
    /**
     * 通过批量的key获取批量的value
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param keys
     * @return       java.util.List<java.lang.Object>
     * @throws
     */
    public List<Object> mget(Collection<String> keys){

        if(null==keys){
            return Collections.emptyList();
        }
        return redisTemplate.opsForValue().multiGet(keys);
    }
    /**
     *  将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。
     *  如果Value的值不能转换为整型值,如Hi,该操作将执行失败并抛出相应异常。操作成功则返回增加后的value值。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param increment
     * @return       long
     * @throws
     */
    public long incrby(String key,long increment){
        if(null==key){
            throw new BusinessException(4001004,"key不能为空");
        }
        return redisTemplate.opsForValue().increment(key,increment);
    }
    /**
     *
     * 将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,
     * 在decrby之后其值为-decrement。如果Value的值不能转换为整型值,
     * 如Hi,该操作将执行失败并抛出相应的异常。操作成功则返回减少后的value值。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param decrement
     * @return       java.lang.Long
     * @throws
     */
    public Long decrby(String key,long decrement){
        if(null==key){
            throw new BusinessException(4001004,"key不能为空");
        }
        return redisTemplate.opsForValue().decrement(key,decrement);
    }
    /**
     *  如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,
     *  APPEND命令将会创建一个新的Key/Value。返回追加后Value的字符串长度。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param value
     * @return       java.lang.Integer
     * @throws
     */
    public Integer append(String key,String value){
        if(key==null){
            throw new BusinessException(4001004,"key不能为空");
        }
        return redisTemplate.opsForValue().append(key,value);
    }
//******************hash数据类型*********************
    /**
     * 通过key 和 field 获取指定的 value
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param field
     * @return       java.lang.Object
     * @throws
     */
    public Object hget(String key, Object field) {
        if(null==key||null==field){
            return null;
        }
        return redisTemplate.opsForHash().get(key,field);
    }

    /**
     * 为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以用于存储参数中的Field/Value对,
     * 如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。
     * 返回1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param field
     * @param value
     * @return
     * @throws
     */
    public void hset(String key, Object field, Object value) {
        if(null==key||null==field){
            return;
        }
        redisTemplate.opsForHash().put(key,field,value);
    }

    /**
     * 判断指定Key中的指定Field是否存在,返回true表示存在,false表示参数中的Field或Key不存在。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param field
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean hexists(String key, Object field) {
        if(null==key||null==field){
            return false;
        }
        return redisTemplate.opsForHash().hasKey(key,field);
    }

    /**
     * 从指定Key的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略,
     * 返回实际删除的Field数量。如果Key不存在,则将其视为空Hashes,并返回0。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param fields
     * @return       java.lang.Long
     * @throws
     */
    public Long hdel(String key, Object... fields) {
        if(null==key||null==fields||fields.length==0){
            return 0L;
        }
        return redisTemplate.opsForHash().delete(key,fields);
    }


    /**
     * 通过key获取所有的field和value
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.util.Map<java.lang.Object,java.lang.Object>
     * @throws
     */
    public Map<Object, Object> hgetall(String key) {
        if(key==null){
            return null;
        }
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。
     * 如果Key不存在,则创建新Key,同时设定参数中的Field/Value。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param hash
     * @return
     * @throws
     */
    public void hmset(String key, Map<String, Object> hash) {

        if(null==key||null==hash){
            return;
        }
        redisTemplate.opsForHash().putAll(key,hash);
    }

    /**
     * 获取和参数中指定Fields关联的一组Values,其返回顺序等同于Fields的请求顺序。
     * 如果请求的Field不存在,其值对应的value为null。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param fields
     * @return       java.util.List<java.lang.Object>
     * @throws
     */
    public List<Object> hmget(String key, Collection<Object> fields) {

        if(null==key||null==fields){
            return null;
        }

        return redisTemplate.opsForHash().multiGet(key,fields);
    }

    /**
     * 对应key的字段自增相应的值
     * @Author:     刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param field
     * @param increment
     * @return       java.lang.Long
     * @throws
     */
    public Long hIncrBy(String key,Object field,long increment){
        if (null==key||null==field){
            throw new BusinessException(4001004,"key or field 不能为空");
        }
        return redisTemplate.opsForHash().increment(key,field,increment);

    }
    //***************List数据类型***************
    /**
     * 向列表左边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部插入。
     * 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param strs 可以使一个string 也可以使string数组
     * @return       java.lang.Long 返回操作的value个数
     * @throws
     */
    public Long lpush(String key, Object... strs) {
        if(null==key){
            return 0L;
        }
        return redisTemplate.opsForList().leftPushAll(key,strs);
    }

    /**
     * 向列表右边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。
     * 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param strs 可以使一个string 也可以使string数组
     * @return       java.lang.Long 返回操作的value个数
     * @throws
     */
    public Long rpush(String key, Object... strs) {
        if(null==key){
            return 0L;
        }
        return redisTemplate.opsForList().rightPushAll(key,strs);
    }
    /**
     * 返回并弹出指定Key关联的链表中的第一个元素,即头部元素。如果该Key不存在,
     * 返回nil。LPOP命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Object
     * @throws
     */
    public Object lpop(String key) {
        if(null==key){
            return null;
        }
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 返回并弹出指定Key关联的链表中的最后一个元素,即头部元素。如果该Key不存在,返回nil。
     * RPOP命令执行两步操作:第一步是将列表右边的元素从列表中移除,第二步是返回被移除的元素值。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Object
     * @throws
     */
    public Object rpop(String key) {
        if(null==key){
            return null;
        }
        return redisTemplate.opsForList().rightPop(key);
    }

    /**
     *该命令的参数start和end都是0-based。即0表示链表头部(leftmost)的第一个元素。
     * 其中start的值也可以为负值,-1将表示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。
     * 该命令在获取元素时,start和end位置上的元素也会被取出。如果start的值大于链表中元素的数量,
     * 空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括start)开始,链表中剩余的所有元素。
     * 注:Redis的列表起始索引为0。显然,LRANGE numbers 0 -1 可以获取列表中的所有元素。返回指定范围内元素的列表。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param start
     * @param end
     * @return       java.util.List<java.lang.Object>
     * @throws
     */
    public List<Object> lrange(String key, long start, long end) {
        if(null==key){
            return null;
        }
        return redisTemplate.opsForList().range(key,start,end);
    }

    /**
     * 让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
     * 下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
     * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param start
     * @param end
     * @return
     * @throws
     */
    public void ltrim(String key, long start, long end) {
        if(null==key){
            return;
        }
        redisTemplate.opsForList().trim(key,start,end);
    }

    /**
     * 该命令将返回链表中指定位置(index)的元素,index是0-based,表示从头部位置开始第index的元素,
     * 如果index为-1,表示尾部元素。如果与该Key关联的不是链表,该命令将返回相关的错误信息。 如果超出index返回这返回nil。
     * @Author:     刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param index
     * @return       java.lang.Object
     * @throws
     */
    public Object lindex(String key, long index) {
        if(null==key){
            return null;
        }
        return redisTemplate.opsForList().index(key,index);
    }

    /**
     * 返回指定Key关联的链表中元素的数量,如果该Key不存在,则返回0。如果与该Key关联的Value的类型不是链表,则抛出相关的异常。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Long
     * @throws
     */
    public Long llen(String key) {

        if(null==key){
            return 0L;
        }
        return redisTemplate.opsForList().size(key);
    }
    //***************Set数据类型*************
    /**
     * 如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。
     * 如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。返回实际插入的成员数量。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param members 可以是一个String 也可以是一个String数组
     * @return       java.lang.Long 添加成功的个数
     * @throws
     */
    public Long sadd(String key, Object... members) {
        if (null==key){
            return 0L;
        }
        return redisTemplate.opsForSet().add(key, members);

    }

    /**
     * 返回Set中成员的数量,如果该Key并不存在,返回0。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Long
     * @throws
     */
    public Long scard(String key) {
        if (null==key){
            return 0L;
        }
        return redisTemplate.opsForSet().size(key);

    }

    /**
     * 判断参数中指定成员是否已经存在于与Key相关联的Set集合中。返回true表示已经存在,false表示不存在,或该Key本身并不存在。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param member
     * @return       java.lang.Boolean
     * @throws
     */
    public Boolean sismember(String key, Object member) {
        if (null==key){
            return false;
        }
        return redisTemplate.opsForSet().isMember(key,member);

    }

    /**
     * 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.String
     * @throws
     */
    public Object srandmember(String key) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForSet().randomMember(key);

    }
    /**
     * 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。
     * 还可以传递count参数来一次随机获得多个元素,根据count的正负不同,具体表现也不同。
     * 当count 为正数时,SRANDMEMBER 会随机从集合里获得count个不重复的元素。
     * 如果count的值大于集合中的元素个数,则SRANDMEMBER 会返回集合中的全部元素。
     * 当count为负数时,SRANDMEMBER 会随机从集合里获得|count|个的元素,如果|count|大与集合中的元素,
     * 就会返回全部元素不够的以重复元素补齐,如果key不存在则返回nil。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param count
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    public List<Object> srandmember(String key,int count) {
        if(null==key){
            return null;
        }
        return redisTemplate.opsForSet().randomMembers(key,count);

    }

    /**
     * 通过key随机删除一个set中的value并返回该值
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.String
     * @throws
     */
    public Object spop(String key) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForSet().pop(key);

    }

    /**
     * 通过key获取set中所有的value
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<Object> smembers(String key) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForSet().members(key);

    }
    /**
     * 从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,
     * 如果该Key并不存在,将视为空Set处理。返回从Set中实际移除的成员数量,如果没有则返回0。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param members
     * @return       java.lang.Long
     * @throws
     */
    public Long srem(String key, Object... members) {
        if (null==key){
            return 0L;
        }
        return redisTemplate.opsForSet().remove(key,members);

    }

    /**
     * 将元素value从一个集合移到另一个集合
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param srckey
     * @param dstkey
     * @param member
     * @return       java.lang.Long
     * @throws
     */
    public Boolean smove(String srckey, String dstkey, Object member) {
        if (null==srckey||null==dstkey){
            return false;
        }
        return redisTemplate.opsForSet().move(srckey,member,dstkey);

    }


    /**
     * 获取两个集合的并集
     * @Author:   刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param otherKeys
     * @return       java.util.Set<java.lang.Object> 返回两个集合合并值
     * @throws
     */
    public Set<Object> sUnion(String key, String otherKeys) {
        if (null==key||otherKeys==null){
            return null;
        }
        return redisTemplate.opsForSet().union(key, otherKeys);
    }
    //**********Sorted Set 数据类型********************
    /**
     *添加参数中指定的所有成员及其分数到指定key的Sorted Set中,在该命令中我们可以指定多组score/member作为参数。
     * 如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。
     * 如果键不存在,该命令将为该键创建一个新的Sorted Set Value,并将score/member对插入其中。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param score
     * @param member
     * @return       java.lang.Long
     * @throws
     */
    public Boolean zadd(String key, double score, Object member) {
        if (null==key){
            return false;
        }
        return redisTemplate.opsForZSet().add(key,member,score);

    }


    /**
     * 该命令将移除参数中指定的成员,其中不存在的成员将被忽略。
     * 如果与该Key关联的Value不是Sorted Set,相应的错误信息将被返回。 如果操作成功则返回实际被删除的成员数量。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param members 可以使一个string 也可以是一个string数组
     * @return       java.lang.Long
     * @throws
     */
    public Long zrem(String key, Object... members) {
        if(null==key||null==members){
            return 0L;
        }
        return redisTemplate.opsForZSet().remove(key,members);

    }

    /**
     * 返回Sorted Set中的成员数量,如果该Key不存在,返回0。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @return       java.lang.Long
     * @throws
     */
    public Long zcard(String key) {
        if (null==key){
            return 0L;
        }
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,
     * 此后再将其分数加上increment。如果Key不存在,该命令将创建该Key及其关联的Sorted Set,
     * 并包含参数指定的成员,其分数为increment参数。如果与该Key关联的不是Sorted Set类型,
     * 相关的错误信息将被返回。如果不报错则以串形式表示的新分数。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param score
     * @param member
     * @return       java.lang.Double
     * @throws
     */
    public Double zincrby(String key, double score, Object member) {
        if (null==key){
            throw new BusinessException(4001004,"key 不能为空");
        }
        return redisTemplate.opsForZSet().incrementScore(key,member,score);
    }

    /**
     * 该命令用于获取分数(score)在min和max之间的成员数量。
     * (min=<score<=max)如果加上了“(”着表明是开区间例如zcount key (min max 则 表示(min<score=<max)
     * 同理zcount key min (max 则表明(min=<score<max) 返回指定返回数量。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param min
     * @param max
     * @return       java.lang.Long
     * @throws
     */
    public Long zcount(String key, double min, double max) {
        if (null==key){
            return 0L;
        }
        return redisTemplate.opsForZSet().count(key, min, max);

    }

    /**
     * Sorted Set中的成员都是按照分数从低到高的顺序存储,该命令将返回参数中指定成员的位置值,
     * 其中0表示第一个成员,它是Sorted Set中分数最低的成员。 如果该成员存在,则返回它的位置索引值。否则返回nil。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param member
     * @return       java.lang.Long
     * @throws
     */
    public Long zrank(String key, Object member) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForZSet().rank(key,member);

    }

    /**
     * 如果该成员存在,以字符串的形式返回其分数,否则返回null
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param member
     * @return       java.lang.Double
     * @throws
     */
    public Double zscore(String key, Object member) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForZSet().score(key,member);
    }

    /**
     * 该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示最后一个成员。如果start大于该Sorted
     * Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索引值,
     * 该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,
     * 该命令在返回的结果中将包含每个成员的分数值,如value1,score1,value2,score2...。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param min
     * @param max
     * @return       java.util.Set<java.lang.String> 指定区间内的有序集成员的列表。
     * @throws
     */
    public Set<Object> zrange(String key, long min, long max) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForZSet().range(key, min, max);

    }
    /**
     * 该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,
     * 即从高到低的顺序。如果成员具有相同的分数,则按降序字典顺序排序。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param start
     * @param end
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<Object> zReverseRange(String key, long start, long end) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForZSet().reverseRange(key, start, end);

    }

    /**
     * 该命令将返回分数在min和max之间的所有成员,即满足表达式min <= score <= max的成员,
     * 其中返回的成员是按照其分数从低到高的顺序返回,如果成员具有相同的分数,
     * 则按成员的字典顺序返回。可选参数LIMIT用于限制返回成员的数量范围。
     * 可选参数offset表示从符合条件的第offset个成员开始返回,同时返回count个成员。
     * 可选参数WITHSCORES的含义参照ZRANGE中该选项的说明。*最后需要说明的是参数中min和max的规则可参照命令ZCOUNT。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param max
     * @param min
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<Object> zrangebyscore(String key, double min, double max) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);

    }



    /**
     * 该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。
     * 需要注意的是该命令中的min和max参数的顺序和ZRANGEBYSCORE命令是相反的。
     * @Author:      刘阳洋
     * @UpdateUser:
     * @Version:     0.0.1
     * @param key
     * @param max
     * @param min
     * @return       java.util.Set<java.lang.String>
     * @throws
     */
    public Set<Object> zrevrangeByScore(String key, double min, double max) {
        if (null==key){
            return null;
        }
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }
}

SwaggerConfig:

package com.example.distributed.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:47
 */

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createDocket(){
        List<Parameter> parameterList=new ArrayList<>();
        ParameterBuilder parameterBuilder=new ParameterBuilder();
        parameterBuilder.name("token").description("swagger调试用(模拟传入用户认证凭证)").modelRef(new ModelRef("String"))
                .parameterType("header").required(false);
        parameterList.add(parameterBuilder.build());
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.distributed.demo"))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(parameterList)
                ;
    }
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder().
                title("Spring Boot 2")
                .description("刘阳洋")
                .version("1.0")
                .build();
    }


}

WebAppConfig 拦截器策略

package com.example.distributed.demo.config;

import com.example.distributed.demo.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:51
 */
//配置拦截器策略
@Configuration
public class WebAppConfig implements WebMvcConfigurer {

    @Bean
    public TokenInterceptor tokenInterceptor(){
        return new TokenInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/user/login","/api/user/register","/api/user/code/*");
    }
}

6、常用参数类

package com.example.distributed.demo.content;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:53
 */

public class Content{

        /**
         * 判断是否达上线的key
         */
        public final static String REGISTER_CODE_COUNT_KEY="register-code-count-key_";
        /**
         * 验证码有效期key
         */
        public final static String REGISTER_CODE_COUNT_VALIDITY_KEY="register-code-count-validity-key_";


        /**
         * 订单编码
         */
        public final static String ORDER_CODE_KEY="order-code-key_";

        /**
         * 购物车key
         */
        public final static String CART_KEY="cart_";
}

7、exception包

package com.example.distributed.demo.exception;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:03
 */

public class BusinessException extends RuntimeException{

    private final int messageCode;

    private final String messageDefault;

    public BusinessException(int messageCode,String message ) {
        super(message);
        this.messageCode = messageCode;
        this.messageDefault = message;
    }

    public int getMessageCode() {
        return messageCode;
    }

    public String getMessageDefault() {
        return messageDefault;
    }
}

8、inteceptor包

package com.example.distributed.demo.interceptor;

import com.example.distributed.demo.exception.BusinessException;
import com.example.distributed.demo.service.impl.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:52
 */

public class TokenInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token=request.getHeader("token");
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(3004,"用户凭证不能为空,请重新登录");
        }else {
            if(!redisService.hasKey(token)){
                throw new BusinessException(3005,"用户凭证无效,请重新登录");
            }
            String userId= (String) redisService.get(token);
            if(redisService.hasKey(userId)&&!token.equals(redisService.get(userId))){
                throw new BusinessException(3006,"您的账号已经在异地登录,请重新登录");
            }
        }
        return true;
    }

}

9、redis templete序列化 类

package com.example.distributed.demo.serializer;

import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 16:16
 */

public class MyStringRedisSerializer implements RedisSerializer<Object> {

    private final Charset charset;

    public MyStringRedisSerializer() {
        this(StandardCharsets.UTF_8);
    }

    public MyStringRedisSerializer(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }

    @Override
    public String deserialize(byte[] bytes) {
        return (bytes == null ? null : new String(bytes, charset));
    }

    @Override
    public byte[] serialize(Object object) {
        if (object == null) {
            return new byte[0];
        }
        if (object instanceof String) {
            return object.toString().getBytes(charset);
        } else {
            String string = JSON.toJSONString(object);
            return string.getBytes(charset);
        }
    }
}

10、utils包

package com.example.distributed.demo.utils;

import java.security.MessageDigest;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 18:11
 */

public class PasswordEncoder {

    private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
            "e", "f" };

    private final static String MD5 = "MD5";
    private final static String SHA = "SHA";

    private Object salt;
    private String algorithm;

    public PasswordEncoder(Object salt) {
        this(salt, MD5);
    }

    public PasswordEncoder(Object salt, String algorithm) {
        this.salt = salt;
        this.algorithm = algorithm;
    }

    /**
     * 密码加密
     * @param rawPass
     * @return
     */
    public String encode(String rawPass) {
        String result = null;
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            // 加密后的字符串
            result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));
        } catch (Exception ex) {
        }
        return result;
    }

    /**
     * 密码匹配验证
     * @param encPass 密文
     * @param rawPass 明文
     * @return
     */
    public boolean matches(String encPass, String rawPass) {
        String pass1 = "" + encPass;
        String pass2 = encode(rawPass);

        return pass1.equals(pass2);
    }

    private String mergePasswordAndSalt(String password) {
        if (password == null) {
            password = "";
        }

        if ((salt == null) || "".equals(salt)) {
            return password;
        } else {
            return password + "{" + salt.toString() + "}";
        }
    }

    /**
     * 转换字节数组为16进制字串
     *
     * @param b
     *            字节数组
     * @return 16进制字串
     */
    private String byteArrayToHexString(byte[] b) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    /**
     * 将字节转换为16进制
     * @param b
     * @return
     */
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n = 256 + n;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static void main(String[] args) {

    }

}

package com.example.distributed.demo.utils;

import java.util.UUID;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 18:11
 */

public class PasswordUtils {
    /**
     * 匹配密码
     * @param salt 盐
     * @param rawPass 明文
     * @param encPass 密文
     * @return
     */
    public static boolean matches(String salt, String rawPass, String encPass) {
        return new PasswordEncoder(salt).matches(encPass, rawPass);
    }

    /**
     * 明文密码加密
     * @param rawPass 明文
     * @param salt
     * @return
     */
    public static String encode(String rawPass, String salt) {
        return new PasswordEncoder(salt).encode(rawPass);
    }

    /**
     * 获取加密盐
     * @return
     */
    public static String getSalt() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
    }
}

11、vo包

request:

package com.example.distributed.demo.vo.request;

import io.swagger.annotations.ApiModelProperty;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:52
 */

public class AddCartReqVO {

    @ApiModelProperty(value = "商品skuId")
    private String skuId;
    @ApiModelProperty(value = "属性规格id拼接集合(以逗号隔开)")
    private String specificationIds;
    @ApiModelProperty(value = "商品数量")
    private Integer num;

    public String getSkuId() {
        return skuId;
    }

    public void setSkuId(String skuId) {
        this.skuId = skuId;
    }

    public String getSpecificationIds() {
        return specificationIds;
    }

    public void setSpecificationIds(String specificationIds) {
        this.specificationIds = specificationIds;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }
}

package com.example.distributed.demo.vo.request;

import io.swagger.annotations.ApiModelProperty;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 17:54
 */

public class LoginReqVO {

    @ApiModelProperty(value = "用户名")
    private String username;
    @ApiModelProperty(value = "密码")
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "LoginReqVO{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

package com.example.distributed.demo.vo.request;

import io.swagger.annotations.ApiModelProperty;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 17:28
 */

public class RegisterReqVO {


    @ApiModelProperty(value = "账号")
    private String username;
    @ApiModelProperty(value = "手机号")
    private String phone;
    @ApiModelProperty(value = "密码")
    private String password;
    @ApiModelProperty(value = "验证码")
    private String code;



    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

response包:

package com.example.distributed.demo.vo.response;

import io.swagger.annotations.ApiModelProperty;

import java.math.BigDecimal;
import java.util.List;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:54
 */

public class CartRespVO {
    @ApiModelProperty("商品skuId")
    private String skuId;
    @ApiModelProperty(value = "商品名称")
    private String productName;
    @ApiModelProperty(value = "规格属性")
    private List<ValueItemRespVO> itemRespVOList;
    @ApiModelProperty(value = "单价")
    private BigDecimal price;
    @ApiModelProperty(value = "数量")
    private Integer num;
    @ApiModelProperty(value = "图标")
    private String icon;

    public String getSkuId() {
        return skuId;
    }

    public void setSkuId(String skuId) {
        this.skuId = skuId;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public List<ValueItemRespVO> getItemRespVOList() {
        return itemRespVOList;
    }

    public void setItemRespVOList(List<ValueItemRespVO> itemRespVOList) {
        this.itemRespVOList = itemRespVOList;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getIcon() {
        return icon;
    }

    public void setIcon(String icon) {
        this.icon = icon;
    }
}

package com.example.distributed.demo.vo.response;

import io.swagger.annotations.ApiModelProperty;

import java.math.BigDecimal;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:55
 */

public class GoodsItemRespVO {

    @ApiModelProperty(value = "商品skuId")
    private String skuId;
    @ApiModelProperty(value = "商品名称")
    private String productName;
    @ApiModelProperty(value = "价格")
    private BigDecimal price;

    @ApiModelProperty(value = "图标")
    private String icon;

    public String getSkuId() {
        return skuId;
    }

    public void setSkuId(String skuId) {
        this.skuId = skuId;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public String getIcon() {
        return icon;
    }

    public void setIcon(String icon) {
        this.icon = icon;
    }
}

package com.example.distributed.demo.vo.response;

import io.swagger.annotations.ApiModelProperty;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 17:55
 */

public class LoginRespVO {

    @ApiModelProperty(value = "用户认证凭证")
    private String token;

    @ApiModelProperty(value = "用户id")
    private String userId;


    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "LoginRespVO{" +
                "token='" + token + '\'' +
                ", userId='" + userId + '\'' +
                '}';
    }
}

package com.example.distributed.demo.vo.response;

import io.swagger.annotations.ApiModelProperty;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:54
 */

public class ValueItemRespVO {
    @ApiModelProperty(value = "属性id")
    private String valueId;
    @ApiModelProperty(value = "商品规格属性名称")
    private String valueName;
    @ApiModelProperty(value = "商品规格属性类型名称")
    private String typeName;
    @ApiModelProperty(value = "商品规格属性类型")
    private String type;

    public String getValueId() {
        return valueId;
    }

    public void setValueId(String valueId) {
        this.valueId = valueId;
    }

    public String getValueName() {
        return valueName;
    }

    public void setValueName(String valueName) {
        this.valueName = valueName;
    }

    public String getTypeName() {
        return typeName;
    }

    public void setTypeName(String typeName) {
        this.typeName = typeName;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

12、service层
接口包:

package com.example.distributed.demo.service.intefacer;

import com.example.distributed.demo.vo.request.AddCartReqVO;
import com.example.distributed.demo.vo.response.CartRespVO;

import java.util.List;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:53
 */

public interface CartService {
    String addCart(AddCartReqVO vo, String userId);
    List<CartRespVO> showCart(String userId);
}

package com.example.distributed.demo.service.intefacer;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:32
 */

public interface CodeService {
    String getOrderCode(String type);
}

package com.example.distributed.demo.service.intefacer;

import com.example.distributed.demo.vo.request.LoginReqVO;
import com.example.distributed.demo.vo.request.RegisterReqVO;
import com.example.distributed.demo.vo.response.LoginRespVO;
import com.example.distributed.demo.entity.SysUser;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 18:02
 */

public interface UserService {

    LoginRespVO login(LoginReqVO vo);

    SysUser getUserInfo(String id);

    String register(RegisterReqVO vo);

    String getCode(String phone);
}

实现类:

package com.example.distributed.demo.service.impl;

import com.example.distributed.demo.content.Content;
import com.example.distributed.demo.service.intefacer.CartService;
import com.example.distributed.demo.vo.request.AddCartReqVO;
import com.example.distributed.demo.vo.response.CartRespVO;
import com.example.distributed.demo.vo.response.GoodsItemRespVO;
import com.example.distributed.demo.vo.response.ValueItemRespVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:56
 */

/**
 * 加入购物车功能
 */
@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private RedisService redisService;


    @Override
    public String addCart(AddCartReqVO vo, String userId) {
        String filed=vo.getSkuId()+","+vo.getSpecificationIds();
        redisService.hIncrBy(Content.CART_KEY+userId,filed,vo.getNum());
        return "操作成功";
    }

    @Override
    public List<CartRespVO> showCart(String userId) {
        //获取用户购物车所有数据

        Map<Object, Object> maps = redisService.hgetall(Content.CART_KEY + userId);
        //解析数据
        List<CartRespVO> result = new ArrayList<>();
        if (null == maps) {
            return result;
        }

        // 1. entrySet遍历,在键和值都需要时使用(最常用)
        for (Map.Entry<Object, Object> entry : maps.entrySet()) {
            CartRespVO respVO = new CartRespVO();
            //商品数量
            Integer num = Integer.valueOf(entry.getValue().toString());
            String file = (String) entry.getKey();
            //获取商品skuId和商品熟悉id(数组第一个为skuId)
            String ids[] = file.split(",");
            //查询商品相信信息
            String skuId = ids[0];

            //拿到商品详细
            GoodsItemRespVO itemVO = getItem(skuId);
            respVO.setIcon(itemVO.getIcon());
            respVO.setIcon(itemVO.getIcon());
            respVO.setPrice(itemVO.getPrice());
            respVO.setSkuId(itemVO.getSkuId());
            respVO.setProductName(itemVO.getProductName());
            respVO.setNum(num);

            List<ValueItemRespVO> list=new ArrayList<>();
            for (int i=1;i<ids.length;i++){
                //拿属性值
                ValueItemRespVO item=getValueItem(ids[i]);
                list.add(item);
            }
            respVO.setItemRespVOList(list);
            result.add(respVO);

        }
        return result;
    }


    private GoodsItemRespVO getItem(String skuId) {
        //这里是 mock 数据
        GoodsItemRespVO respVO=new GoodsItemRespVO();
        respVO.setIcon("http://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%9B%BE%E7%89%87&hs=0&pn=0&spn=0&di=7590&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=1411728850%2C1869975885&os=69787666%2C250391253&simid=3412044283%2C207046138&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=&objurl=http%3A%2F%2Fpic18.nipic.com%2F20120103%2F8993051_170340691334_2.jpg&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bgtrtv_z%26e3Bv54AzdH3Ffi5oAzdH3F9AzdH3F8a0AzdH3Fccb9nlmhbbcud8kj_z%26e3Bip4s&gsm=&islist=&querylist=");
        respVO.setPrice(new BigDecimal(69.99));
        respVO.setSkuId(skuId);
        respVO.setProductName("spring boot 实战");
        return respVO;
    }

    private ValueItemRespVO getValueItem(String id){
        //这里是 mock 数据
        ValueItemRespVO respVO=new ValueItemRespVO();
        respVO.setType("1");
        respVO.setTypeName("普通属性");
        respVO.setValueName("VIP 教学");
        respVO.setValueId(id);
        return respVO;
    }
}

package com.example.distributed.demo.service.impl;

import com.example.distributed.demo.content.Content;
import com.example.distributed.demo.service.intefacer.CodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:32
 */

/**
 * redis实现计数器(订单号/特殊有规律编码/点赞数)功能
 */
@Service
public class CodeServiceImpl implements CodeService {

    @Autowired
    private RedisService redisService;
    @Override
    public String getOrderCode(String type) {

        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");
        String date=simpleDateFormat.format(System.currentTimeMillis());
        Long number=redisService.incrby(Content.ORDER_CODE_KEY+date,1);

        String pad=padRight(number.toString(),7,"0");

        return date+type+pad;
    }


    private String padRight(String oldStr,int len,String alexin){
        String str="";
        int strlen=oldStr.length();
        for(int i=0;i<len-strlen;i++){
            str=str+alexin;
        }
        str=str+oldStr;
        return str;
    }
}

package com.example.distributed.demo.service.impl;

import com.example.distributed.demo.content.Content;
import com.example.distributed.demo.exception.BusinessException;
import com.example.distributed.demo.service.intefacer.UserService;
import com.example.distributed.demo.utils.PasswordUtils;
import com.example.distributed.demo.vo.request.LoginReqVO;
import com.example.distributed.demo.vo.request.RegisterReqVO;
import com.example.distributed.demo.vo.response.LoginRespVO;

import com.example.distributed.demo.entity.SysUser;
import com.example.distributed.demo.mapper.SysUserDao;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import java.util.Date;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 18:03
 */

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private RedisService redisService;

    @Autowired
    private SysUserDao sysUserDao;

    /**
     * 用户注册
     * @param vo
     * @return
     */

    @Override
    public String register(RegisterReqVO vo) {

        //判断验证码是否有效
        if(!redisService.hasKey(Content.REGISTER_CODE_COUNT_VALIDITY_KEY+vo.getPhone())){
            throw new BusinessException(3010,"验证码已失效请重新获取");
        }
        //校验验证码是否正确
        if(!vo.getCode().equals(redisService.get(Content.REGISTER_CODE_COUNT_VALIDITY_KEY+vo.getPhone()))){
            throw new BusinessException(3011,"请输入正确的验证码");
        }
        SysUser sysUser = sysUserDao.selectByUsername(vo.getUsername());
        if(sysUser!=null){
            throw new BusinessException(3012,"该账号已被占用");
        }

        SysUser user=new SysUser();
        BeanUtils.copyProperties(vo,user);
        user.setId(UUID.randomUUID().toString());
        user.setCreateTime(new Date());
        String salt=PasswordUtils.getSalt();
        String ecdPwd=PasswordUtils.encode(vo.getPassword(),salt);
        user.setSalt(salt);
        user.setPassword(ecdPwd);
        int i = sysUserDao.insertSelective(user);
        if(i!=1){
            throw new BusinessException(3013,"操作失败");
        }

        redisService.delete(Content.REGISTER_CODE_COUNT_VALIDITY_KEY+vo.getPhone());
        redisService.delete(Content.REGISTER_CODE_COUNT_KEY+vo.getPhone());
        return "注册成功";
    }



    /**
     * 用户登录
     * @param vo
     * @return
     */

    @Override
    public LoginRespVO login(LoginReqVO vo) {

        SysUser sysUser = sysUserDao.selectByUsername(vo.getUsername());
        if (sysUser == null) {
            throw new BusinessException(3001, "不存在该用户,请先注册");

        }

        if (sysUser.getStatus() == 2) {
            throw new BusinessException(3002, "该账号已被禁用请联系系统管理员");
        }
        if (!PasswordUtils.matches(sysUser.getSalt(), vo.getPassword(), sysUser.getPassword())) {
            throw new BusinessException(3003, "用户名密码不匹配");
        }

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

        LoginRespVO loginRespVO = new LoginRespVO();
        loginRespVO.setUserId(sysUser.getId());
        loginRespVO.setToken(token);

        //分布式session凭证存入redis 60分钟失效
        redisService.set(token,sysUser.getId(),60, TimeUnit.MINUTES);
        //异地登录提醒下线
        redisService.set(sysUser.getId(),token,60,TimeUnit.MINUTES);

        return  loginRespVO;

    }

    @Override
    public SysUser getUserInfo(String id) {
        return sysUserDao.selectByPrimaryKey(id);
    }


    /**
     * 短信验证码功能
     * @param phone
     * @return
     */
    @Override
    public String getCode(String phone) {


        //验证手机号是否合法
        Pattern pattern = Pattern.compile("^1(3|4|5|7|8)\\d{9}$");
        Matcher matcher = pattern.matcher(phone);
        if(!matcher.matches()) {
            throw  new BusinessException(3014,"手机号格式错误");
        }
        //判断手机号是否超限
        long count = redisService.incrby(Content.REGISTER_CODE_COUNT_KEY+phone,1);
        if(count>5){
            throw new BusinessException(3015,"当日发送已达上限");
        }

        //生成6位随机数
        String code=generateCode();
        //发送短信(具体根据你们公司所用的api文档来)
        //存入 redis 过期时间为 5 分钟
        redisService.set(Content.REGISTER_CODE_COUNT_VALIDITY_KEY+phone,code,5,TimeUnit.MINUTES);
        //发送短信这里用输出模拟
        System.out.println(code);
        return code;
    }



    /**
     * 生成六位验证码
     * @return
     */
    private String generateCode(){
        Random random = new Random();
        int x = random.nextInt(899999);
        String code = String.valueOf(x + 100000);
        return code;
    }
}

13、controller层

package com.example.distributed.demo.controller;

import com.example.distributed.demo.service.impl.RedisService;
import com.example.distributed.demo.service.intefacer.CartService;
import com.example.distributed.demo.vo.request.AddCartReqVO;
import com.example.distributed.demo.vo.response.CartRespVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 22:03
 */

@RestController
@RequestMapping("/api")
@Api(tags = "购物车模块",description = "购物车模块相关接口")
public class CartController {

    @Autowired
    private RedisService redisService;
    @Autowired
    private CartService cartService;
    @PostMapping("/cart")
    @ApiOperation(value = "加入购物车")
    public String addCart(@RequestBody AddCartReqVO vo, HttpServletRequest request){
        String token=request.getHeader("token");
        String userId= (String) redisService.get(token);
        return cartService.addCart(vo,userId);
    }

    @GetMapping("/cart")
    @ApiOperation(value = "获取用户购物车数据")
    public List<CartRespVO> showCart(HttpServletRequest request){
        String token=request.getHeader("token");
        String userId= (String) redisService.get(token);
        System.out.println(userId);
        return cartService.showCart(userId);
    }
}

package com.example.distributed.demo.controller;

import com.example.distributed.demo.service.intefacer.CodeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 21:41
 */


@RestController
@RequestMapping("/api")
@Api(tags = "订单模块",description = "订单模块相关接口")
public class OrderController {
    @Autowired
    private CodeService codeService;
    @GetMapping("/order/code/{type}")
    @ApiOperation(value = "生产订单编码")
    public String getOrderCode(@PathVariable("type") String type){
        return codeService.getOrderCode(type);
    }
}

package com.example.distributed.demo.controller;

import com.example.distributed.demo.entity.SysUser;
import com.example.distributed.demo.service.intefacer.UserService;
import com.example.distributed.demo.vo.request.LoginReqVO;
import com.example.distributed.demo.vo.request.RegisterReqVO;
import com.example.distributed.demo.vo.response.LoginRespVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author 刘阳洋
 * @date 2020/5/17 0017 17:03
 */
@RestController
@RequestMapping("/api")
@Api(tags = "用户模块",description = "用户模块相关接口")
public class UserController {


    //http://localhost:8889/swagger-ui.html
    // http://localhost:8889/api/user/register
    @Autowired
    private UserService userService;

    @PostMapping("/user/register")
    @ApiOperation(value = "用户注册接口")
    public String register(@RequestBody RegisterReqVO vo){

        return userService.register(vo);
    }
    // http://localhost:8889/api/user/login
    @PostMapping("/user/login")
    @ApiOperation(value = "用户登录接口")
    public LoginRespVO login(@RequestBody LoginReqVO vo){
        return userService.login(vo);
    }


    @GetMapping("/user/{id}")
    @ApiOperation(value = "获取用户信息接口")
    public SysUser getUserInfo(@PathVariable("id") String id){
        return userService.getUserInfo(id);
    }



    @GetMapping("/user/code/{phone}")
    public String getCode(@PathVariable("phone") String phone){
        return userService.getCode(phone);
    }
}




14、启动类

package com.example.distributed.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.distributed.demo.mapper")
public class DemoApplication {

    public static void main(String[] args) {


        SpringApplication.run(DemoApplication.class, args);
        System.out.println("启动成功");
    }

}

15测试类

package com.example.distributed.demo;

import com.example.distributed.demo.entity.User;
import com.example.distributed.demo.service.impl.RedisService;
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.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest

public class DemoApplicationTests {

    @Autowired
    private RedisService redisService;
    @Test
    public void testRedis() {

        User user=new User();
        user.setUsername("admin");
        user.setPassowrd("刘阳洋");
        redisService.set("user",user);
        System.out.println("结果为:" + redisService.get("user"));
    }

}

16 、测试
登录 swagger进行测试
http://localhost:8889/swagger-ui.html
http://localhost:8889/api/user/register
http://localhost:8889/api/user/login

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值