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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章