The email sending function (the custom launcher of springBoot) teaches you how to make a mailbox

catalogue

1, Introduction to stater

1. The concept of stater:

2. Implementation of stat:

3. Schematic diagram of stat:

2, java operation mail (use of mail)

Steps:

1. Import dependency

2. Rewrite yml resource file

3. Write the test

4. Test

3, Mailbox creation (custom launcher)

Steps:

1. Import dependency first

2. Create yml file

Get authorization code link: what is an authorization code and how is it set_ QQ email Help Center

3. Create entity class resource file

4. Define a sending interface and implement the interface

5. Test

4, Use mailbox

1. Improve the flexibility of mailbox

Steps:

1. Import yml file and inject dependency

2. Add a file under the resource file

2. Package the whole project into jar package

3. Import mailbox dependencies into previous projects (the same local warehouse must be used)

4. Add notes to the test class

5. Test: run successfully

5, Implementation of operation in combination with redis

Step 1

1. Import depends on redis

2. In application Add redis resource to YML resource file

3. Write the help class into the project

4. Create a new entity class and control class

2. Optimize mailbox usage

Solution:

Steps:

1, Introduction to stater

1. The concept of stater:

starter will include all the dependencies used, avoiding the trouble caused by developers introducing dependencies themselves. It should be noted that different starters are used to solve different dependencies, so their internal implementations may be very different. For example, jpa starters and Redis starters may have different implementations. This is because the essence of starters lies in synthesize, which is an abstraction at the logical level. Maybe this concept is a bit similar to Docker, Because they are all doing a "packaging" operation.

2. Implementation of stat:

Although different starters have different implementations, they basically use two identical contents: ConfigurationProperties and AutoConfiguration. Because SpringBoot firmly believes in the concept of "Convention is greater than configuration", we use ConfigurationProperties to save our configurations, and these configurations can have a default value, that is, the default value will take effect without actively overwriting the original configuration, which is very useful in many cases. In addition, the ConfigurationProperties of starter also enables all the configuration properties to be aggregated into one file (generally application.properties under the resources directory), so we bid farewell to the XML hell in the Spring project.

3. Schematic diagram of stat:

Note: the most important one is the configuration file. Users can customize the configuration file and write their own email address to improve mailbox flexibility

2, java operation mail (use of mail)

Steps:

1. Import dependency

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

2. Rewrite yml resource file

spring:
  application:
    name: springBoot_06
  mail:
    host: smtp.qq.com
    username: self qq@qq.com
    password: exgdllmxqrhrieae
    properties:
     mail:
      smtp:
       auth: true
       starttls:
        enable: true
        required: true

3. Write the test

package com.zj.code;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;

@SpringBootTest
class SpringBoot06ApplicationTests {

    @Autowired
    JavaMailSender mailSender;

    @Test
    void contextLoads() {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("1551506470@qq.com");
        message.setTo("1551506470@qq.com");
        message.setSubject("Subject: Simple Mail");
        message.setText("Test email content");
        mailSender.send(message);


    }
}

Note: in the test class, special attention should be paid to the following annotations

@Autowired
JavaMailSender mailSender;

4. Test

Test successful

3, Mailbox creation (custom launcher)

Steps:

1. Import dependency first

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

2. Create yml file

Note: in the yml file, the four attributes mean

email:
 enable: true -- whether to enable mailbox verification
 host: smtp. qq. Host -- com
 username: self qq@qq.com --Account number (sender)
 password: uqoimlnpljuqihdd -- password (note that here is the authorization code, not the password)

Get authorization code link: What is the authorization code and how is it set_ QQ email Help Center

3. Create entity class resource file

EmailProperties: 
package com.yzm.yzmspringbootstarter;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author T440s
 */


@NoArgsConstructor
@AllArgsConstructor
@Data
@ConfigurationProperties(prefix= "email")
public class EmailProperties {

    private String host;
    private String username;
    private String password;
    private String enable;

}

Description of the four notes:

@NoArgsConstructor: nonparametric construction
@AllArgsConstructor: parametric construction
@Data: get and set methods
@ConfigurationProperties(prefix= "email") function: complete yml injection according to the properties (bean objects need to be managed by spring container)

4. Define a sending interface and implement the interface

Interface: EmailSender:
package com.yzm.yzmspringbootstarter;

public interface EmailSender {
    String sendText(String receiver);
}

Interface implementation class:

EmailSenderImpl: 
package com.yzm.yzmspringbootstarter;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
@Slf4j
@EnableConfigurationProperties(value = EmailProperties.class)
@ConditionalOnProperty(prefix = "email", value = "enable", havingValue = "true")
public class EmailSenderImpl implements EmailSender {

    private final EmailProperties emailProperties;
    private final JavaMailSenderImpl mailSender;


    @Autowired
    public EmailSenderImpl(EmailProperties emailProperties) {
        this.emailProperties = emailProperties;
        mailSender = new JavaMailSenderImpl();
        mailSender.setHost(emailProperties.getHost());
        mailSender.setUsername(emailProperties.getUsername());
        mailSender.setPassword(emailProperties.getPassword());
        mailSender.setDefaultEncoding("Utf-8");
        Properties p = new Properties();
        p.setProperty("mail.smtp.auth", "true");
        p.setProperty("mail.smtp.starttl .enable", "true");
        p.setProperty("mail.smtp.starttls.required", "true");
        mailSender.setJavaMailProperties(p);
    }


    @Override//This method is to send verification code and display the receiver
    public String sendText(String receiver) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(emailProperties.getUsername());
        message.setTo(receiver);
        message.setSubject("Website verification code");
        message.setText("aabb");
        mailSender.send(message);
        return "aabb";
    }

}

Description of these four notes:

@Configuration: declare this class as a component and leave it to spring for management
@Slf4j: log output
@EnableConfigurationProperties(value = EmailProperties.class) completes the Componentization of corresponding elements
@ConditionalOnProperty(prefix = "email", value = "enable", havingValue = "true")

Controls whether the mail function is available

5. Test

Test class:

package com.yzm.yzmspringbootstarter;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class  YzmSpringBootStarterApplicationTests {


    @Autowired
    private EmailSender sender;

    @Test
    void contextLoads() {
        sender.sendText("1551506470@qq.com");
    }
}

Test successful

Note: in the process of making mailbox, two problems need to be paid attention to:

1. The format of email in yml file should be standardized (one space should be left in the middle)

()()email:
 enable: true
 host: smtp.qq.com
 username: self qq@qq.com
 password: uqoimlnpljuqihdd

2. Both the account number and the recipient's are written with qq number + qq COM, not the account when the email was created

username: self qq@qq.com

4, Use mailbox

1. Improve the flexibility of mailbox

Since everyone's mailbox is different, it is impossible to fix the mailbox of the sender and the recipient and whether to enable the mailbox. Therefore, the content in yml should be changed to improve the flexibility of mailbox use.

Steps:

1. Import yml file and inject dependency

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
     </dependency>


     <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.1</version>
                <configuration><classifier>exec</classifier></configuration>
     </plugin>


2. Add a file under the resource file

META-INF > spring.factories

Document content:

org.springframework.boot.autoconfigure.

EnableAutoConfiguration=com.yzm.yzmspringbootstarter.EmailSenderImpl

Description of this Code: when the whole project runs, the corresponding class will run with it

Check whether the file was added successfully:

2. Package the whole project into jar package

Finally, go to the local warehouse to see if yzm this folder (named by yourself) can be viewed in the pom file

<modelVersion>4.0.0</modelVersion>
<groupId>com.yzm</groupId>
<artifactId>yzm-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>yzm-spring-boot-starter</name>
<description>yzm-spring-boot-starter</description>

Package succeeded.

3. Import mailbox dependencies into previous projects (the same local warehouse must be used)

<dependency>
    <groupId>com.yzm</groupId>
    <artifactId>yzm-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

4. Add notes to the test class

@Autowired
private EmailSender emailSender;

An error will be reported when importing this annotation. Reason: there is no resource file configured

spring:
  application:
    name: springBoot_06
email:
  host: smtp.qq.com
  username: self qq@qq.com
  password: exgdllmxqrhrieae

After adding the basic configuration file, an error is still reported (reason: the mailbox is not opened, and there is a prompt indicating that the import dependency is successful)

Import succeeded

5. Test: run successfully

5, Implementation of operation in combination with redis

Due to the large amount of data, it is impossible to enter the database to get the data every time. At this time, the cache will be used to improve the running speed of the program.    

Step 1

1. Import depends on redis

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

2. In application Add redis resource to YML resource file

  redis:
    database: 0         #Database index
    host: 127.0.0.1     #Host location
    port: 6379          #port
    password:            #password
    jedis:
      pool:
        max-active: 8   #maximum connection
        max-wait: -1    #Maximum blocking waiting time (negative number means no limit)
        max-idle: 8     #Maximum idle
        min-idle: 0     #Minimum idle
    timeout: 10000      #Connection timeout

3. Write the help class into the project

conf--->CrossConfiguration

package com.zj.code.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @author Yin ho
 */
@Configuration
public class CrossConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                /*Paths that can span domains*/
                .addMapping("/**")
                /*Cross domain ip*/
                .allowedOrigins("*")
                /*Cross domain approach*/
                .allowedMethods("*")
                /*If there are too many pre inspection requests, it will be invalid*/
                .maxAge(6000)
                /*Head allowed to carry*/
                .allowedHeaders("*");
    }

}
conf--->RedisConfiguration
package com.zj.code.conf;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.time.Duration;

/**
 * @author Yin ho
 */
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {

    @Bean
    @Primary
    CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .computePrefixWith(cacheName -> cacheName + ":-cache-:")
                /*Set cache expiration time*/
                .entryTtl(Duration.ofHours(1))
                /*Disable caching null values and do not cache null checks*/
                .disableCachingNullValues()
                /*Set the value serialization method of CacheManager to json serialization, which can be added with @ Class attribute*/
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()
                ));
        /*Create RedisCacheManager using RedisCacheConfiguration*/
        RedisCacheManager manager = RedisCacheManager.builder(factory)
                .cacheDefaults(cacheConfiguration)
                .build();
        return manager;
    }

    @Bean
    @Primary
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);
        RedisSerializer stringSerializer = new StringRedisSerializer();
        /* key serialize */
        redisTemplate.setKeySerializer(stringSerializer);
        /* value serialize */
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        /* Hash key serialize */
        redisTemplate.setHashKeySerializer(stringSerializer);
        /* Hash value serialize */
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    @Primary
    @Override
    public KeyGenerator keyGenerator() {
        return (Object target, Method method, Object... params) -> {
            final int NO_PARAM_KEY = 0;
            final int NULL_PARAM_KEY = 53;
            StringBuilder key = new StringBuilder();
            /* Class.Method: */
            key.append(target.getClass().getSimpleName())
                    .append(".")
                    .append(method.getName())
                    .append(":");
            if (params.length == 0) {
                return key.append(NO_PARAM_KEY).toString();
            }
            int count = 0;
            for (Object param : params) {
                /* Parameters are separated by */
                if (0 != count) {
                    key.append(',');
                }
                if (param == null) {
                    key.append(NULL_PARAM_KEY);
                } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                    int length = Array.getLength(param);
                    for (int i = 0; i < length; i++) {
                        key.append(Array.get(param, i));
                        key.append(',');
                    }
                } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
                    key.append(param);
                } else {
                    /*JavaBean Be sure to override hashCode and equals*/
                    key.append(param.hashCode());
                }
                count++;
            }
            return key.toString();
        };
    }

}

util-->RedisUtil

package com.zj.code.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * Redis Tool class
 * <p>
 * Disclaimer: This tool simply wraps most of the commonly used APIs of redisTemplate, and does not wrap all of the APIs of redisTemplate
 * If you are not satisfied with the functions in this tool class or the api provided by StringRedisTemplate,
 * Then you can implement the corresponding execute method in the corresponding {@ link StringRedisTemplate} class to achieve
 * To achieve the desired effect; As for how to implement it, you can refer to the method in the source code or {@ link LockOps}
 * <p>
 * Note: This tool class depends on the spring boot starter data redis class library
 * Note: see {@ link RedisOperations} for more javadoc details
 * <p>
 * Unified description 1: the key and value in the method cannot be null
 * Unified description 2: you cannot operate across data types. Otherwise, the operation will fail / an error will be reported
 * For example, a Hash operation to a String type will fail / report an error wait
 */
@Slf4j
@Component
@SuppressWarnings("unused")
public class RedisUtil implements ApplicationContextAware {

    /**
     * Use StringRedisTemplate(, which is a customized upgrade of RedisTemplate)
     */
    private static StringRedisTemplate redisTemplate;

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
    }

    /**
     * key Related operation
     */
    public static class KeyOps {

        /**
         * Delete the corresponding redis value in the key
         * <p>
         * Note: if deletion fails, false will be returned
         * <p>
         * If the key does not exist in redis, the returned value is false
         * Therefore, you can't assume that there must still exist in redis just because false is returned
         * In the key value corresponding to the key
         *
         * @param key key to delete
         * @return Is the deletion successful
         */
        public static Boolean delete(String key) {
            log.info("delete(...) => key -> {}", key);
            // The return value can only be true/false, not null
            Boolean result = redisTemplate.delete(key);
            log.info("delete(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Delete key values in batches according to keys
         * <p>
         * Note: if there is no corresponding key in redis, the count will not be increased by 1, that is:
         * redis In the key value existing in, the keys named a1 and a2,
         * When deleting, the set passed is a1, a2 and a3, and the returned result is 2
         *
         * @param keys key collection to delete
         * @return Number of key values deleted
         */
        public static long delete(Collection<String> keys) {
            log.info("delete(...) => keys -> {}", keys);
            Long count = redisTemplate.delete(keys);
            log.info("delete(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Serialize the value value corresponding to the key and return the serialized value
         * <p>
         * Note: if there is no corresponding key, null will be returned
         * Note: during dump, the corresponding key value in redis will not be deleted
         * Note: the function is opposite to that of restore dump
         *
         * @param key key of value to be serialized
         * @return Serialized value
         */
        public static byte[] dump(String key) {
            log.info("dump(...) =>key -> {}", key);
            byte[] result = redisTemplate.dump(key);
            log.info("dump(...) => result -> {}", result);
            return result;
        }

        /**
         * Deserialize the given value into redis to form a new key value
         *
         * @param key        value Corresponding key
         * @param value      The value value of the inverse sequence to be
         *                   Note: this value can be obtained from {@ link this#dump(String)}
         * @param timeToLive Lifetime of key value after deserialization
         * @param unit       timeToLive Unit of
         * @throws RedisSystemException This exception is thrown if the same key already exists in redis
         */
        public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {
            restore(key, value, timeToLive, unit, false);
        }

        /**
         * Deserialize the given value into redis to form a new key value
         *
         * @param key     value Corresponding key
         * @param value   The value value of the inverse sequence to be
         *                Note: this value can be obtained from {@ link this#dump(String)}
         * @param timeout Lifetime of key value after deserialization
         * @param unit    timeout Unit of
         * @param replace If the same key already exists in redis, do you want to replace the original key value
         * @throws RedisSystemException This exception is thrown if the same key already exists in redis and replace is false
         */
        public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {
            log.info("restore(...) => key -> {},value -> {},timeout -> {},unit -> {},replace -> {}",
                    key, value, timeout, unit, replace);
            redisTemplate.restore(key, value, timeout, unit, replace);
        }

        /**
         * redis Specify the key value of the key
         *
         * @param key Specified key
         * @return Is there a corresponding key value
         */
        public static boolean hasKey(String key) {
            log.info("hasKey(...) => key -> {}", key);
            Boolean result = redisTemplate.hasKey(key);
            log.info("hasKey(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Set the key value corresponding to the specified key: how long is it obsolete
         * <p>
         * Note: redis will automatically delete the corresponding key value after it becomes obsolete
         * Note: if the key does not exist, false will also be returned
         *
         * @param key     Specified key
         * @param timeout Obsolete time
         * @param unit    timeout Unit of
         * @return Is the operation successful
         */
        public static boolean expire(String key, long timeout, TimeUnit unit) {
            log.info("expire(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
            Boolean result = redisTemplate.expire(key, timeout, unit);
            log.info("expire(...) => result is -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Set the key value corresponding to the specified key: when is it obsolete
         * <p>
         * Note: redis will automatically delete the corresponding key value after it becomes obsolete
         * Note: if the key does not exist, false will also be returned
         *
         * @param key  Specified key
         * @param date When is it out of date
         * @return Is the operation successful
         */
        public static boolean expireAt(String key, Date date) {
            log.info("expireAt(...) => key -> {},date -> {}", key, date);
            Boolean result = redisTemplate.expireAt(key, date);
            log.info("expireAt(...) => result is -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Find all keys matching the pattern and return the combination of the key
         * <p>
         * Tip: if there are many key value pairs in redis, this method takes a long time. Use it with caution! Use with caution! Use with caution!
         *
         * @param pattern Matching template
         *                Note: common wildcards are:
         *                ?    There is and only one;
         *                *     >=0 Number;
         * @return The collection of key s matching pattern may be null
         */
        public static Set<String> keys(String pattern) {
            log.info("keys(...) => pattern -> {}", pattern);
            Set<String> keys = redisTemplate.keys(pattern);
            log.info("keys(...) => keys -> {}", keys);
            return keys;
        }

        /**
         * Move the key value corresponding to the key in the current database to the database in the corresponding location
         * <p>
         * Note: for the stand-alone version of redis, the storage is divided into 16 dB by default, and the index is 0 to 15
         * Note: under the same db, the key is unique; But in different db, the key can be the same
         * Note: if the same key already exists in the target db, move will fail and return false
         *
         * @param key     Locate the key of the key value to be moved
         * @param dbIndex Which db do you want to move to
         * @return Is the move successful
         * Note: if the same key already exists in the target db, move will fail and return false
         */
        public static boolean move(String key, int dbIndex) {
            log.info("move(...) => key  -> {},dbIndex -> {}", key, dbIndex);
            Boolean result = redisTemplate.move(key, dbIndex);
            log.info("move(...) =>result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Remove the expiration time of the key value corresponding to the key so that the key value always exists
         * <p>
         * Note: if the key value corresponding to the key itself always exists (without expiration time), the persist method will return false;
         * If no key value corresponding to the key exists, the persist method will return false;
         *
         * @param key Locate the key of key value
         * @return Is the operation successful
         */
        public static boolean persist(String key) {
            log.info("persist(...) => key -> {}", key);
            Boolean result = redisTemplate.persist(key);
            log.info("persist(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get the expiration time of the key value corresponding to the key
         * <p>
         * Note: if the key value never expires, the returned value is - 1
         * Note: if there is no key value corresponding to key, the returned value is - 2
         * Note: if there are fractional times less than 1 SECONDS, round (roughly) to the SECONDS level
         *
         * @param key Locate the key of key value
         * @return Expiration time (unit: s)
         */
        public static long getExpire(String key) {
            return getExpire(key, TimeUnit.SECONDS);
        }

        /**
         * Get the expiration time of the key value corresponding to the key
         * <p>
         * Note: if the key value never expires, the returned value is - 1
         * Note: if there is no key value corresponding to key, the returned value is - 2
         * Note: if the fragmentary time is less than 1 unit, it shall be rounded to unit (generally)
         *
         * @param key Locate the key of key value
         * @return Expiration time (unit)
         */
        public static long getExpire(String key, TimeUnit unit) {
            log.info("getExpire(...) =>key -> {},unit is -> {}", key, unit);
            Long result = redisTemplate.getExpire(key, unit);
            log.info("getExpire(...) => result ->  {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Randomly obtain a key from all keys in redis
         * <p>
         * Note: if there is no key value in redis, null will be returned here
         *
         * @return A randomly obtained key
         */
        public static String randomKey() {
            String result = redisTemplate.randomKey();
            log.info("randomKey(...) => result is -> {}", result);
            return result;
        }

        /**
         * Rename the corresponding oldKey to a new newKey
         * <p>
         * Note: if the oldKey does not exist, an exception will be thrown
         * Note: if the same key as newKey already exists in redis,
         * Then the original key value will be discarded,
         * Only the new key and the original value are left
         * Example description: Suppose (keyAlpha,valueAlpha) and (keyBeta,valueBeat) exist in redis,
         * After using rename(keyAlpha,keyBeta) to replace, only (keyBeta,valueAlpha) will be left in redis
         *
         * @param oldKey Old key
         * @param newKey New key
         * @throws RedisSystemException Throw this exception if the oldKey does not exist
         */
        public static void rename(String oldKey, String newKey) {
            log.info("rename(...) => oldKey -> {},newKey -> {}", oldKey, newKey);
            redisTemplate.rename(oldKey, newKey);
        }

        /**
         * When there is no newKey in redis, rename the corresponding oldKey to a new newKey
         * If not, do not rename
         * <p>
         * Note: if the oldKey does not exist, an exception will be thrown
         *
         * @param oldKey Old key
         * @param newKey New key
         * @throws RedisSystemException Throw this exception if the oldKey does not exist
         */
        public static boolean renameIfAbsent(String oldKey, String newKey) {
            log.info("renameIfAbsent(...) => oldKey -> {},newKey -> {}", oldKey, newKey);
            Boolean result = redisTemplate.renameIfAbsent(oldKey, newKey);
            log.info("renameIfAbsent(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get the data type of value corresponding to key
         * <p>
         * Note: if the key value corresponding to the key does not exist in redis, then NONE is returned here
         *
         * @param key key for positioning
         * @return key The data type of the corresponding value
         */
        public static DataType type(String key) {
            log.info("type(...) => key -> {}", key);
            DataType result = redisTemplate.type(key);
            log.info("type(...) => result -> {}", result);
            return result;
        }
    }

    /**
     * string Related operation
     */
    public static class StringOps {

        /**
         * Set key value
         * <p>
         * Note: if the same key already exists, the original key value will be discarded
         *
         * @param key   key
         * @param value key Corresponding value
         */
        public static void set(String key, String value) {
            log.info("set(...) => key -> {},value -> {}", key, value);
            redisTemplate.opsForValue().set(key, value);
        }

        /**
         * Process the value value corresponding to the key in redis and set the value of the offset bit to 1 or 0
         * <p>
         * Note: in redis, all stored strings exist in a two-level system; For example, in the stored key value, the value is abc. In fact,
         * In redis, 01100010110001001100011 is stored. The first 8 bits correspond to a, the middle 8 bits correspond to b, and the last 8 bits correspond to c
         * Example: if setBit(key,6,true) is set to the number of index position 6, the value will become 1
         * 011000110110001001100011
         * Note: offset is index, starting from 0
         * <p>
         * Note: if the parameter value is true, it is set to 1; If the parameter value is false, it is set to 0
         * <p>
         * Note: if there is no corresponding key in redis, a new key will be created automatically
         * Note: offset can exceed the index length of value in binary
         *
         * @param key    Locate the key of value
         * @param offset Index of bit to be changed
         * @param value  Change to 1 or 0,true - change to 1,false - change to 0
         * @return set Is it successful
         */
        public static boolean setBit(String key, long offset, boolean value) {
            log.info("setBit(...) => key -> {},offset -> {},value -> {}", key, offset, value);
            Boolean result = redisTemplate.opsForValue().setBit(key, offset, value);
            log.info("setBit(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Set key value
         * <p>
         * Note: if the same key already exists, the original key value will be discarded
         *
         * @param key     key
         * @param value   key Corresponding value
         * @param timeout Obsolescence duration
         * @param unit    timeout Unit of
         */
        public static void setEx(String key, String value, long timeout, TimeUnit unit) {
            log.info("setEx(...) => key -> {},value -> {},timeout -> {},unit -> {}",
                    key, value, timeout, unit);
            redisTemplate.opsForValue().set(key, value, timeout, unit);
        }

        /**
         * If there is no key, add key value to redis and return success / failure
         * If it exists, no operation will be performed and false will be returned
         *
         * @param key   key
         * @param value key Corresponding value
         * @return set Is it successful
         */
        public static boolean setIfAbsent(String key, String value) {
            log.info("setIfAbsent(...) => key -> {},value -> {}", key, value);
            Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
            log.info("setIfAbsent(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * If there is no key, add a key value (with timeout duration) to redis and return success / failure
         * If it exists, no operation will be performed and false will be returned
         *
         * @param key     key
         * @param value   key Corresponding value
         * @param timeout Timeout duration
         * @param unit    timeout Unit of
         * @return set Is it successful
         */
        public static boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
            log.info("setIfAbsent(...) => key -> {},value -> {},key -> {},value -> {}", key, value, timeout, unit);
            Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
            log.info("setIfAbsent(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * From the offset position of value (corresponding to key in redis) (including this position), replace the value of the corresponding length with replaceValue
         * <p>
         * For example:
         * 1.Suppose there is a key value ("DS", "0123456789") in redis; transfer
         * After setRange("ds","abcdefghijk",3) is used, the value in redis becomes [012abcdefghijk]
         * <p>
         * 2.Suppose there is a key value ("JD", "0123456789") in redis; transfer
         * After setRange("jd","xyz",3) is used, the value in redis becomes [012xyz6789]
         * <p>
         * 3.Suppose there is a key value ("ey", "0123456789") in redis; transfer
         * After setRange("ey","qwer",15) is used, the value in redis becomes [0123456789 qwer]
         * Note: case3 is special. If the offset exceeds the length of the original value, there will be some spaces to fill in the middle, but if it is in the program
         * If it is directly output in, the space in the middle may be garbled
         *
         * @param key          Locate the key of key value
         * @param replaceValue Value to replace
         * @param offset       Starting position
         */
        public static void setRange(String key, String replaceValue, long offset) {
            log.info("setRange(...) => key -> {},replaceValue -> {},offset -> {}", key, replaceValue, offset);
            redisTemplate.opsForValue().set(key, replaceValue, offset);
        }

        /**
         * Get the length of the value corresponding to the key
         * <p>
         * Note: the length is equal to {@ link String#length}
         * Note: if the corresponding key value does not exist in redis, the return value is 0
         *
         * @param key Locate the key of value
         * @return value Length of
         */
        public static long size(String key) {
            log.info("size(...) => key -> {}", key);
            Long result = redisTemplate.opsForValue().size(key);
            log.info("size(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Batch setting key value
         * <p>
         * Note: if the same key exists, the original key value will be discarded
         *
         * @param maps key-value collection
         */
        public static void multiSet(Map<String, String> maps) {
            log.info("multiSet(...) => maps -> {}", maps);
            redisTemplate.opsForValue().multiSet(maps);
        }

        /**
         * When there is no key in redis, set the key value in batch and return success / failure
         * If not, no operation will be performed and false will be returned
         * <p>
         * That is, suppose that the parameter map passed in when calling this method is as follows: {k1=v1,k2=v2,k3=v3}
         * In redis, the key value will be set in batch only when k1, k2 and k3 do not exist;
         * Otherwise, no key value will be set
         * <p>
         * Note: if the same key exists, the original key value will be discarded
         * <p>
         * Note:
         *
         * @param maps key-value collection
         * @return Is the operation successful
         */
        public static boolean multiSetIfAbsent(Map<String, String> maps) {
            log.info("multiSetIfAbsent(...) => maps -> {}", maps);
            Boolean result = redisTemplate.opsForValue().multiSetIfAbsent(maps);
            log.info("multiSetIfAbsent(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Increment / decrement integer
         * <p>
         * Note: negative numbers are minus
         * Note: if the value value corresponding to the key does not support the increase / decrease operation (i.e. value is not a number), then
         * Throw out org springframework. data. redis. RedisSystemException
         *
         * @param key       key used to locate value
         * @param increment How much more
         * @return Total value after increase
         * @throws RedisSystemException key When the corresponding value does not support increase / decrease operation
         */
        public static long incrBy(String key, long increment) {
            log.info("incrBy(...) => key -> {},increment -> {}", key, increment);
            Long result = redisTemplate.opsForValue().increment(key, increment);
            log.info("incrBy(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Increase / decrease floating point number
         * <p>
         * Note: use floating point numbers with caution, there will be accuracy problems
         * For example: redisutil StringOps. set("ds","123");
         * Then redisutil StringOps. incrByFloat("ds",100.6);
         * You will see the problem of accuracy
         * Note: negative numbers are minus
         * Note: if the value value corresponding to the key does not support the increase / decrease operation (i.e. value is not a number), then
         * Throw out org springframework. data. redis. RedisSystemException
         *
         * @param key       key used to locate value
         * @param increment How much more
         * @return Total value after increase
         * @throws RedisSystemException key When the corresponding value does not support increase / decrease operation
         */
        public static double incrByFloat(String key, double increment) {
            log.info("incrByFloat(...) => key -> {},increment -> {}", key, increment);
            Double result = redisTemplate.opsForValue().increment(key, increment);
            log.info("incrByFloat(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Append value to end
         * <p>
         * Note: if there is no key in redis, then (in effect) this method is equivalent to {@ link this#set(String, String)}
         *
         * @param key   Locate the key of value
         * @param value value to append
         * @return After appending, the length of the whole value
         */
        public static int append(String key, String value) {
            log.info("append(...) => key -> {},value -> {}", key, value);
            Integer result = redisTemplate.opsForValue().append(key, value);
            log.info("append(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get the corresponding value value according to the key
         *
         * @param key key-value Corresponding key
         * @return The value corresponding to this key
         * Note: if the key does not exist, null will be returned
         */
        public static String get(String key) {
            log.info("get(...) => key -> {}", key);
            String result = redisTemplate.opsForValue().get(key);
            log.info("get(...) => result -> {} ", result);
            return result;
        }

        /**
         * Intercept the value corresponding to (key), and the interception range is [start,end]
         * <p>
         * Note: if the range of [start,end] is not in the range of value, the empty string "" will be returned
         * Note: if only a part of value is in the range of [start,end], then the content of the corresponding part of value will be returned (that is, the insufficient part will not be filled with empty)
         *
         * @param key   Locate the key of value
         * @param start Start position (from 0)
         * @param end   End position (starting from 0)
         * @return Intercepted string
         */
        public static String getRange(String key, long start, long end) {
            log.info("getRange(...) => kry -> {}", key);
            String result = redisTemplate.opsForValue().get(key, start, end);
            log.info("getRange(...) => result -> {} ", result);
            return result;
        }

        /**
         * Set a new value for the specified key and return the old value
         * <p>
         * Note: if there is no key in redis, the operation can still succeed, but the old value returned is null
         *
         * @param key      Locate the key of value
         * @param newValue The new value of the key to be set to
         * @return Old value value
         */
        public static String getAndSet(String key, String newValue) {
            log.info("getAndSet(...) => key -> {},value -> {}", key, newValue);
            String oldValue = redisTemplate.opsForValue().getAndSet(key, newValue);
            log.info("getAndSet(...) => oldValue -> {}", oldValue);
            return oldValue;
        }

        /**
         * Get the bit value of the offset position of (key corresponding) value in binary
         * <p>
         * Note: when the value of offset is outside the index range (value under binary), the returned value is also false
         * <p>
         * Example:
         * RedisUtil.StringOps.set("akey","a");
         * String a, converted to binary 0110001
         * Then the result obtained by getBit("akey",6) is false
         *
         * @param key    Locate the key of value
         * @param offset Index of positioning bit
         * @return offset bit value corresponding to position (true - 1, false - 0)
         */
        public static boolean getBit(String key, long offset) {
            log.info("getBit(...) => key -> {},offset -> {}", key, offset);
            Boolean result = redisTemplate.opsForValue().getBit(key, offset);
            log.info("getBit(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get value value in batch
         * <p>
         * Note: if the corresponding key does not exist in redis, the returned value corresponding to the key is null
         *
         * @param keys key collection
         * @return value Value set
         */
        public static List<String> multiGet(Collection<String> keys) {
            log.info("multiGet(...) => keys -> {}", keys);
            List<String> result = redisTemplate.opsForValue().multiGet(keys);
            log.info("multiGet(...) => result -> {}", result);
            return result;
        }
    }

    /**
     * hash Related operation
     * <p>
     * Tip: simply, the hash data structure in redis can be regarded as map < string, map < HK, HV > >
     */
    public static class HashOps {

        /**
         * Add a key value pair entrykey entryvalue to the hash corresponding to the key
         * <p>
         * Note: if the same entryKey already exists in the same hash, this operation will discard the original entryKey entryvalue,
         * Instead, use the new entrykey entryvalue
         *
         * @param key        Locate the key of the hash
         * @param entryKey   The key in the key value pair to be added to the hash
         * @param entryValue The value in the key value pair to be added to the hash
         */
        public static void hPut(String key, String entryKey, String entryValue) {
            log.info("hPut(...) => key -> {},entryKey -> {},entryValue -> {}", key, entryKey, entryValue);
            redisTemplate.opsForHash().put(key, entryKey, entryValue);
        }

        /**
         * Add maps to the hash corresponding to the key (that is, add entry sets in batch)
         * <p>
         * Note: if the same entryKey already exists in the same hash, this operation will discard the original entryKey entryvalue,
         * Instead, use the new entrykey entryvalue
         *
         * @param key  Locate the key of the hash
         * @param maps The set of key value pairs to add to the hash
         */
        public static void hPutAll(String key, Map<String, String> maps) {
            log.info("hPutAll(...) => key -> {},maps -> {}", key, maps);
            redisTemplate.opsForHash().putAll(key, maps);
        }

        /**
         * When there is no entryKey in the hash corresponding to the key, the entryKey entryvalue is added (to the hash corresponding to the key)
         * Otherwise, do nothing
         *
         * @param key        Locate the key of the hash
         * @param entryKey   The key in the key value pair to be added to the hash
         * @param entryValue The value in the key value pair to be added to the hash
         * @return Is the operation successful
         */
        public static boolean hPutIfAbsent(String key, String entryKey, String entryValue) {
            log.info("hPutIfAbsent(...) => key -> {},entryKey -> {},entryValue -> {}",
                    key, entryKey, entryValue);
            Boolean result = redisTemplate.opsForHash().putIfAbsent(key, entryKey, entryValue);
            log.info("hPutIfAbsent(...) => result -> {}", result);
            if (result != null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get the value of the corresponding field in the hash corresponding to the key
         * <p>
         * Note: if there is no corresponding key in redis, null will be returned
         * If the corresponding entryKey does not exist in the hash corresponding to the key, null will also be returned
         *
         * @param key      Locate the key of the hash
         * @param entryKey Locate the entryKey of the entryValue in the hash
         * @return key The entryValue value corresponding to the entryKey in the corresponding hash
         */
        public static Object hGet(String key, String entryKey) {
            log.info("hGet(...) => key -> {},entryKey -> {}", key, entryKey);
            Object entryValue = redisTemplate.opsForHash().get(key, entryKey);
            log.info("hGet(...) => entryValue -> {}", entryValue);
            return entryValue;
        }

        /**
         * Get the hash corresponding to the key (i.e. get the map corresponding to the key < HK, HV >)
         * <p>
         * Note: if there is no corresponding key in redis, an empty map without any entry will be returned instead of null
         *
         * @param key Locate the key of the hash
         * @return key Corresponding hash
         */
        public static Map<Object, Object> hGetAll(String key) {
            log.info("hGetAll(...) => key -> {}", key);
            Map<Object, Object> result = redisTemplate.opsForHash().entries(key);
            log.info("hGetAll(...) => result -> {}", result);
            return result;
        }

        /**
         * Get the entryValue of the entryKey in the hash (corresponding to the key) in batch
         * <p>
         * Note: if the corresponding entryKey in the hash does not exist, the returned corresponding entryValue value is null
         * Note: if the key does not exist in redis, then each element in the returned List is null
         * Note: the list itself is not null and the size is not 0, but each element in each list is null
         *
         * @param key       Locate the key of the hash
         * @param entryKeys The field set in the hash to be obtained
         * @return hash Corresponding entryValue set of corresponding entryKeys in
         */
        public static List<Object> hMultiGet(String key, Collection<Object> entryKeys) {
            log.info("hMultiGet(...) => key -> {},entryKeys -> {}", key, entryKeys);
            List<Object> entryValues = redisTemplate.opsForHash().multiGet(key, entryKeys);
            log.info("hMultiGet(...) => entryValues -> {}", entryValues);
            return entryValues;
        }

        /**
         * (Batch) delete the corresponding entrykey entryvalue in the (key corresponding) hash
         * <p>
         * Note: 1. If there is no corresponding key in redis, 0 will be returned;
         * 2,If the entryKey to be deleted does not exist in the hash corresponding to the key, it will not be + 1 in the count, such as:
         * RedisUtil.HashOps.hPut("ds","name","Deng Sullivan ");
         * RedisUtil.HashOps.hPut("ds","birthday","1994-02-05");
         * RedisUtil.HashOps.hPut("ds","hobby","Female ");
         * Redisutil. Is called HashOps. hDelete("ds","name","birthday","hobby","non-exist-entryKey")
         * The returned result is 3
         * Note: if all entries in the hash (corresponding to the key) are deleted, the key will also be deleted
         *
         * @param key       Locate the key of the hash
         * @param entryKeys Locate the entryKey of the entryKey entryvalue to delete
         * @return How many entries in the corresponding hash have been deleted
         */
        public static long hDelete(String key, Object... entryKeys) {
            log.info("hDelete(...) => key -> {},entryKeys -> {}", key, entryKeys);
            Long count = redisTemplate.opsForHash().delete(key, entryKeys);
            log.info("hDelete(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Check whether the entry corresponding to the entryKey exists in the hash (corresponding to the key)
         * <p>
         * Note: if there is no key in redis, false will be returned
         * Note: if there is no corresponding entryKey in the hash corresponding to the key, false will also be returned
         *
         * @param key      Locate the key of the hash
         * @param entryKey Locate the entryKey of the entry in the hash
         * @return hash Whether the entry corresponding to the entryKey exists in the
         */
        public static boolean hExists(String key, String entryKey) {
            log.info("hDelete(...) => key -> {},entryKeys -> {}", key, entryKey);
            Boolean exist = redisTemplate.opsForHash().hasKey(key, entryKey);
            log.info("hDelete(...) => exist -> {}", exist);
            return exist;
        }

        /**
         * Increment / decrement (an entryValue value in hash) integer
         * <p>
         * Note: negative numbers are minus
         * Note: if the key does not exist, the corresponding hash will be automatically created, and the corresponding entryKey and entryvalue will be created. The initial value of entryvalue is increment
         * Note: if the entryKey does not exist, the corresponding entryvalue will be created automatically. The initial value of entryvalue is increment
         * Note: if the value value corresponding to the key does not support the increase / decrease operation (i.e. value is not a number), then
         * Throw out org springframework. data. redis. RedisSystemException
         *
         * @param key       key used to locate hash
         * @param entryKey  entryKey used to locate entryValue
         * @param increment How much more
         * @return Total value after increase
         * @throws RedisSystemException key When the corresponding value does not support increase / decrease operation
         */
        public static long hIncrBy(String key, Object entryKey, long increment) {
            log.info("hIncrBy(...) => key -> {},entryKey -> {},increment -> {}",
                    key, entryKey, increment);
            Long result = redisTemplate.opsForHash().increment(key, entryKey, increment);
            log.info("hIncrBy(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Increment / decrement (an entryValue value in hash) floating point number
         * <p>
         * Note: negative numbers are minus
         * Note: if the key does not exist, the corresponding hash will be automatically created, and the corresponding entryKey and entryvalue will be created. The initial value of entryvalue is increment
         * Note: if the entryKey does not exist, the corresponding entryvalue will be created automatically. The initial value of entryvalue is increment
         * Note: if the value value corresponding to the key does not support the increase / decrease operation (i.e. value is not a number), then
         * Throw out org springframework. data. redis. RedisSystemException
         * Note: because it is a floating point number, there may be accuracy problems like {@ link StringOps#incrByFloat(String, double)}
         * Note: I have simply tested several groups of data, and there is no accuracy problem yet
         *
         * @param key       key used to locate hash
         * @param entryKey  entryKey used to locate entryValue
         * @param increment How much more
         * @return Total value after increase
         * @throws RedisSystemException key When the corresponding value does not support increase / decrease operation
         */
        public static double hIncrByFloat(String key, Object entryKey, double increment) {
            log.info("hIncrByFloat(...) => key -> {},entryKey -> {},increment -> {}",
                    key, entryKey, increment);
            Double result = redisTemplate.opsForHash().increment(key, entryKey, increment);
            log.info("hIncrByFloat(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get all the entrykeys in the (key corresponding) hash
         * <p>
         * Note: if the key does not exist, an empty Set(, not null) will be returned
         *
         * @param key Locate the key of the hash
         * @return hash All entrykeys in
         */
        public static Set<Object> hKeys(String key) {
            log.info("hKeys(...) => key -> {}", key);
            Set<Object> entryKeys = redisTemplate.opsForHash().keys(key);
            log.info("hKeys(...) => entryKeys -> {}", entryKeys);
            return entryKeys;
        }

        /**
         * Get all entryvalues in the hash corresponding to the key
         * <p>
         * Note: if the key does not exist, an empty list (instead of null) will be returned
         *
         * @param key Locate the key of the hash
         * @return hash All entryvalues in
         */
        public static List<Object> hValues(String key) {
            log.info("hValues(...) => key -> {}", key);
            List<Object> entryValues = redisTemplate.opsForHash().values(key);
            log.info("hValues(...) => entryValues -> {}", entryValues);
            return entryValues;
        }

        /**
         * Get the number of all entries in the hash corresponding to the key
         * <p>
         * Note: if there is no corresponding key in redis, the return value is 0
         *
         * @param key Locate the key of the hash
         * @return (key Number of entries in the corresponding) hash
         */
        public static long hSize(String key) {
            log.info("hSize(...) => key -> {}", key);
            Long count = redisTemplate.opsForHash().size(key);
            log.info("hSize(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Match the corresponding entryKey in the (key corresponding) hash according to the options, and return the corresponding entry set
         * <p>
         * <p>
         * Note: the creation method of ScanOptions instance is as follows:
         * 1,ScanOptions.NONE
         * 2,ScanOptions.scanOptions().match("n??e").build()
         *
         * @param key     Locate the key of the hash
         * @param options Conditions matching entryKey
         *                Note: scanoptions None means all matches
         *                Note: scanoptions scanOptions(). match(pattern). Build () means matching according to pattern,
         *                Where the wildcard * can be used in pattern? Wait,
         *                * Indicates > = 0 characters
         *                ? Indicates that there is and only one character
         *                The matching rule here is the same as that at {@ link KeyOps#keys(String)}
         * @return entry in the matched (key corresponding) hash
         */
        @SneakyThrows
        public static Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
            log.info("hScan(...) => key -> {},options -> {}", key, mapper.writeValueAsString(options));
            Cursor<Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, options);
            log.info("hScan(...) => cursor -> {}", mapper.writeValueAsString(cursor));
            return cursor;
        }
    }

    /**
     * list Related operation
     * <p>
     * Tip: the elements in the list can be repeated
     * <p>
     * Tip: the list is ordered
     * <p>
     * Tip: the indexes in the list in redis can be divided into two categories, both of which can be used to locate the elements in the list:
     * Category 1: from left to right, it increases from 0: 0, 1, 2, 3
     * Category 2: from right to left, it starts from - 1 and decreases successively: - 1, - 2, - 3, - 4
     */
    public static class ListOps {

        /**
         * Push elements into the list from the left
         * <p>
         * Note: if there is no corresponding key in redis, it will be created automatically
         *
         * @param key  Locate the key of the list
         * @param item Elements to push into the list
         * @return After pushing, the size of the list corresponding to the key
         */
        public static long lLeftPush(String key, String item) {
            log.info("lLeftPush(...) => key -> {},item -> {}", key, item);
            Long size = redisTemplate.opsForList().leftPush(key, item);
            log.info("lLeftPush(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Push elements into the list in batch from the left end
         * <p>
         * Note: if there is no corresponding key in redis, it will be created automatically
         * Note: in this batch of items, first push the items on the left, and then push the items on the right
         *
         * @param key   Locate the key of the list
         * @param items Set of elements to be pushed into the list in batch
         * @return After pushing, the size of the list (corresponding to the key)
         */
        public static long lLeftPushAll(String key, String... items) {
            log.info("lLeftPushAll(...) => key -> {},items -> {}", key, items);
            Long size = redisTemplate.opsForList().leftPushAll(key, items);
            log.info("lLeftPushAll(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Push elements into the list in batch from the left end
         * <p>
         * Note: if there is no corresponding key in redis, it will be created automatically
         * Note: in this batch of items, which item is taken from the Collection first, which one is push ed first
         *
         * @param key   Locate the key of the list
         * @param items Set of elements to be pushed into the list in batch
         * @return After pushing, the size of the list (corresponding to the key)
         */
        public static long lLeftPushAll(String key, Collection<String> items) {
            log.info("lLeftPushAll(...) => key -> {},items -> {}", key, items);
            Long size = redisTemplate.opsForList().leftPushAll(key, items);
            log.info("lLeftPushAll(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * If there is a key in redis, the elements will be pushed into the list in batch from the left end;
         * Otherwise, do nothing
         *
         * @param key  Locate the key of the list
         * @param item Items to push into the list
         * @return After pushing, the size of the list (corresponding to the key)
         */
        public static long lLeftPushIfPresent(String key, String item) {
            log.info("lLeftPushIfPresent(...) => key -> {},item -> {}", key, item);
            Long size = redisTemplate.opsForList().leftPushIfPresent(key, item);
            log.info("lLeftPushIfPresent(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * If there is a pivot item in the list corresponding to the key, put the item before the first pivot item (that is, to the left of the first pivot item);
         * If the pivot item does not exist in the list corresponding to the key, no operation will be done and - 1 will be returned directly
         * <p>
         * Note: if there is no corresponding key in redis, it will be created automatically
         *
         * @param key  Locate the key of the list
         * @param item Elements to push into the list
         * @return After pushing, the size of the list (corresponding to the key)
         */
        public static long lLeftPush(String key, String pivot, String item) {
            log.info("lLeftPush(...) => key -> {},pivot -> {},item -> {}", key, pivot, item);
            Long size = redisTemplate.opsForList().leftPush(key, pivot, item);
            log.info("lLeftPush(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * It can be compared with {@ link ListOps#lLeftPush(String, String)}, but it is to push elements from the right side of the list
         */
        public static long lRightPush(String key, String item) {
            log.info("lRightPush(...) => key -> {},item -> {}", key, item);
            Long size = redisTemplate.opsForList().rightPush(key, item);
            log.info("lRightPush(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * With {@ link ListOps#lLeftPushAll(String, String...)} Analogy is enough, just push the elements from the right side of the list
         */
        public static long lRightPushAll(String key, String... items) {
            log.info("lRightPushAll(...) => key -> {},items -> {}", key, items);
            Long size = redisTemplate.opsForList().rightPushAll(key, items);
            log.info("lRightPushAll(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * It can be compared with {@ link listops #lleftpushall (string, collection < string >)}, but it is to push elements from the right side of the list
         */
        public static long lRightPushAll(String key, Collection<String> items) {
            log.info("lRightPushAll(...) => key -> {},items -> {}", key, items);
            Long size = redisTemplate.opsForList().rightPushAll(key, items);
            log.info("lRightPushAll(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * It can be compared with {@ link ListOps#lLeftPushIfPresent(String, String)}, but it is to push elements from the right side of the list
         */
        public static long lRightPushIfPresent(String key, String item) {
            log.info("lRightPushIfPresent(...) => key -> {},item -> {}", key, item);
            Long size = redisTemplate.opsForList().rightPushIfPresent(key, item);
            log.info("lRightPushIfPresent(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * It can be compared with {@ link ListOps#lLeftPush(String, String, String)}, but it is to push elements from the right side of the list
         */
        public static long lRightPush(String key, String pivot, String item) {
            log.info("lLeftPush(...) => key -> {},pivot -> {},item -> {}", key, pivot, item);
            Long size = redisTemplate.opsForList().rightPush(key, pivot, item);
            log.info("lLeftPush(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * [Non blocking queue] remove the first element in the list (corresponding to the key) from the left and return the element
         * <p>
         * Note: this method is non blocking, that is, if all the elements in the list (corresponding to the key) are removed by pop, then pop again and null will be returned immediately
         * Note: this method is non blocking, that is, if there is no corresponding key in redis, null will be returned immediately
         * Note: if you pop all the elements in the list (corresponding to the key), the key will be deleted
         *
         * @param key Locate the key of the list
         * @return The element removed
         */
        public static String lLeftPop(String key) {
            log.info("lLeftPop(...) => key -> {}", key);
            String item = redisTemplate.opsForList().leftPop(key);
            log.info("lLeftPop(...) => item -> {}", item);
            return item;
        }

        /**
         * [Block queue] remove the first element in the list (corresponding to the key) from the left and return the element
         * <p>
         * Note: this method is blocked, that is, if all elements in the list (corresponding to the key) are removed by pop, then pop again,
         * Will block timeout for so long and then return null
         * Note: this method is blocked, that is, if the corresponding key does not exist in redis, the timeout will be blocked for so long, and then null will be returned
         * Note: if you pop all the elements in the list (corresponding to the key), the key will be deleted
         * <p>
         * Tip: if the target key list appears and there is an item in it during the blocking process, the blocking will be stopped immediately, the element will be removed and returned
         *
         * @param key     Locate the key of the list
         * @param timeout Timeout
         * @param unit    timeout Unit of
         * @return The element removed
         */
        public static String lLeftPop(String key, long timeout, TimeUnit unit) {
            log.info("lLeftPop(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
            String item = redisTemplate.opsForList().leftPop(key, timeout, unit);
            log.info("lLeftPop(...) => item -> {}", item);
            return item;
        }

        /**
         * It can be compared with {@ link ListOps#lLeftPop(String)}, but it is to remove elements from the right side of the list
         */
        public static String lRightPop(String key) {
            log.info("lRightPop(...) => key -> {}", key);
            String item = redisTemplate.opsForList().rightPop(key);
            log.info("lRightPop(...) => item -> {}", item);
            return item;
        }

        /**
         * It can be compared with {@ link ListOps#lLeftPop(String, long, TimeUnit)}, but it is to remove elements from the right side of the list
         */
        public static String lRightPop(String key, long timeout, TimeUnit unit) {
            log.info("lRightPop(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
            String item = redisTemplate.opsForList().rightPop(key, timeout, unit);
            log.info("lRightPop(...) => item -> {}", item);
            return item;
        }

        /**
         * [[nonblocking queue] remove an item from the right side of the sourceList corresponding to the sourceKey and push the item
         * Enter the left side of the destinationList corresponding to the destinationKey
         * <p>
         * Note: if there is no item in the list corresponding to the sourceKey, the item (pop from the list corresponding to the sourceKey) is immediately considered null,
         * null It does not push into the list corresponding to the destinationKey
         * Note: at this time, the return value of this method is null
         * <p>
         * Note: if you pop all the elements in the list (corresponding to the sourceKey), the sourceKey will be deleted
         *
         * @param sourceKey      Locate the key of sourceList
         * @param destinationKey Locate the key of the destinationList
         * @return Move this element
         */
        public static String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
            log.info("lRightPopAndLeftPush(...) => sourceKey -> {},destinationKey -> {}",
                    sourceKey, destinationKey);
            String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
            log.info("lRightPopAndLeftPush(...) => item -> {}", item);
            return item;
        }

        /**
         * [Block queue] remove an item from the right side of the sourceList corresponding to the sourceKey, and push the item
         * Enter the left side of the destinationList corresponding to the destinationKey
         * <p>
         * Note: if there is no item in the list corresponding to the sourceKey, block and wait until a non null item can be removed from the sourceList (or the waiting time expires);
         * case1: Wait for a non null item, then continue the following push operation and return the item
         * case2: When the timeout expires, the result of pop will not be null before the non null item. At this time, it will not be push ed to the destinationList
         * At this point, the return value of this method is null
         * <p>
         * Note: if you pop all the elements in the list (corresponding to the sourceKey), the sourceKey will be deleted
         *
         * @param sourceKey      Locate the key of sourceList
         * @param destinationKey Locate the key of the destinationList
         * @param timeout        Timeout
         * @param unit           timeout Unit of
         * @return Move this element
         */
        public static String lRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout,
                                                  TimeUnit unit) {
            log.info("lRightPopAndLeftPush(...) => sourceKey -> {},destinationKey -> {},timeout -> {},"
                    + " unit -> {}", sourceKey, destinationKey, timeout, unit);
            String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);
            log.info("lRightPopAndLeftPush(...) => item -> {}", item);
            return item;
        }

        /**
         * Set the element at the corresponding index position in the list (corresponding to the key) as item
         * <p>
         * Note: if the key does not exist, org.org will be thrown springframework. data. redis. RedisSystemException
         * Note: if the index is out of bounds, org.org will also be thrown springframework. data. redis. RedisSystemException
         *
         * @param key   Locate the key of the list
         * @param index Locate the index of the element in the list
         * @param item  Value to replace with
         */
        public static void lSet(String key, long index, String item) {
            log.info("lSet(...) => key -> {},index -> {},item -> {}", key, index, item);
            redisTemplate.opsForList().set(key, index, item);
        }

        /**
         * Get the elements in the list (corresponding to the key) through the index
         * <p>
         * Note: if the key does not exist or the index exceeds the index range of the list (corresponding to the key), null will be returned
         *
         * @param key   Locate the key of the list
         * @param index Locate the index of the item in the list
         * @return list item corresponding to index in index
         */
        public static String lIndex(String key, long index) {
            log.info("lIndex(...) => key -> {},index -> {}", key, index);
            String item = redisTemplate.opsForList().index(key, index);
            log.info("lIndex(...) => item -> {}", item);
            return item;
        }

        /**
         * Get the item set with the index between [start,end] in the list (corresponding to the key)
         * <p>
         * Note: including start and end
         * Note: when the key does not exist, an empty set is obtained
         * Note: when the range obtained is larger than that of the list, the intersection of the two ranges is obtained
         * <p>
         * Tip: you can use redisutil ListOps. Lrange (key, 0, - 1) to get the whole list corresponding to the key
         *
         * @param key   Locate the key of the list
         * @param start index of the starting element
         * @param end   index of the ending element
         * @return Corresponding element set
         */
        public static List<String> lRange(String key, long start, long end) {
            log.info("lRange(...) => key -> {},start -> {},end -> {}", key, start, end);
            List<String> result = redisTemplate.opsForList().range(key, start, end);
            log.info("lRange(...) => result -> {}", result);
            return result;
        }

        /**
         * Get the list corresponding to (key)
         *
         * @param key Locate the key of the list
         * @return (key Corresponding) list
         * @see ListOps#lRange(String, long, long)
         */
        public static List<String> lWholeList(String key) {
            log.info("lWholeList(...) => key -> {}", key);
            List<String> result = redisTemplate.opsForList().range(key, 0, -1);
            log.info("lWholeList(...) => result -> {}", result);
            return result;
        }

        /**
         * Get the size of the list (corresponding to the key)
         * <p>
         * Note: when the key does not exist, the obtained size is 0
         *
         * @param key Locate the key of the list
         * @return list size of
         */
        public static long lSize(String key) {
            log.info("lSize(...) => key -> {}", key);
            Long size = redisTemplate.opsForList().size(key);
            log.info("lSize(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Delete the items with the first expectCount value equal to item in the list (corresponding to key)
         * <p>
         * Note: if expectCount == 0, it means to delete all items in the list whose value is equal to item
         * Note: if expectcount > 0, it means that the deletion is carried out from left to right
         * Note: if expectcount < 0, it means that the deletion is carried out from right to left
         * <p>
         * Note: if the number of items with value equal to item in the list is less than expectCount, all items with value equal to item in the list will be deleted
         * Note: when the key does not exist, it returns 0
         * Note: if there are no elements in the list (corresponding to the key) after lRemove, the key will be deleted
         *
         * @param key         Locate the key of the list
         * @param expectCount Number of item s to delete
         * @param item        item to delete
         * @return Actual number of item s deleted
         */
        public static long lRemove(String key, long expectCount, String item) {
            log.info("lRemove(...) => key -> {},expectCount -> {},item -> {}", key, expectCount, item);
            Long actualCount = redisTemplate.opsForList().remove(key, expectCount, item);
            log.info("lRemove(...) => actualCount -> {}", actualCount);
            if (actualCount == null) {
                throw new RedisOpsResultIsNullException();
            }
            return actualCount;
        }

        /**
         * Clipping (i.e. taking the intersection of the elements in the list)
         * <p>
         * For example: the element index range in the list is [0,8], and the [start,end] passed in by this method is [3,10],
         * Then clipping is to take the intersection of [0,8] and [3,10] to get [3,8]. After clipping
         * In the list, only the elements with indexes between [3,8] are left (before the original clipping)
         * <p>
         * Note: if the cropped list (corresponding to the key) is empty, the key will be deleted
         *
         * @param key   Locate the key of the list
         * @param start Index of the starting item of the item set to be deleted
         * @param end   Index of the end item of the item set to be deleted
         */
        public static void lTrim(String key, long start, long end) {
            log.info("lTrim(...) => key -> {},start -> {},end -> {}", key, start, end);
            redisTemplate.opsForList().trim(key, start, end);
        }

    }

    /**
     * set Related operation
     * <p>
     * Tip: Elements in set cannot be repeated
     * Tip: set is unordered
     * Tip: for the data structure of String in redis, please refer to the data structure of resources / data structure / set (example 1) png
     * redis For the data structure of String in, please refer to the data structure of resources / data structure / set (example 2) png
     */
    public static class SetOps {

        /**
         * Add items to the set corresponding to the key
         * <p>
         * Note: if the key does not exist, it will be created automatically
         * Note: the elements in set will be de duplicated
         *
         * @param key   Locate the key of the set
         * @param items items to be added to the set (corresponding to the key)
         * @return The number of elements added to the set in this addition operation
         */
        public static long sAdd(String key, String... items) {
            log.info("sAdd(...) => key -> {},items -> {}", key, items);
            Long count = redisTemplate.opsForSet().add(key, items);
            log.info("sAdd(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Delete items from the set corresponding to the key
         * <p>
         * Note: if the key does not exist, 0 will be returned
         * Note: if the item in the set (corresponding to the key) has been deleted, the corresponding key will also be deleted
         *
         * @param key   Locate the key of the set
         * @param items items to remove
         * @return Number of actually deleted
         */
        public static long sRemove(String key, Object... items) {
            log.info("sRemove(...) => key -> {},items -> {}", key, items);
            Long count = redisTemplate.opsForSet().remove(key, items);
            log.info("sRemove(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Randomly remove an item from the set (corresponding to the key) and return the item
         * <p>
         * Note: because the set is out of order, the removed item is random; And, even if
         * It is a set with the same data. The removal operation is tested many times, and the removed elements are also random
         * <p>
         * Note: if the item in the set (corresponding to the key) has been pop ped, the corresponding key will be deleted
         *
         * @param key Locate the key of the set
         * @return Removed items
         */
        public static String sPop(String key) {
            log.info("sPop(...) => key -> {}", key);
            String popItem = redisTemplate.opsForSet().pop(key);
            log.info("sPop(...) => popItem -> {}", popItem);
            return popItem;
        }

        /**
         * Move the element item in the sourceSet (corresponding to the sourceKey) to the destinationSet (corresponding to the destinationKey)
         * <p>
         * Note: when sourceKey does not exist, false is returned
         * Note: false is returned when item does not exist
         * Note: if the destinationKey does not exist, it will be created automatically when moving
         * Note: if the item in the set (corresponding to the sourceKey) has been move d out, the corresponding sourceKey will be deleted
         *
         * @param sourceKey      Locate the key of sourceSet
         * @param item           Items to move
         * @param destinationKey Locate the key of the destinationSet
         * @return Whether the move is successful or not
         */
        public static boolean sMove(String sourceKey, String item, String destinationKey) {
            Boolean result = redisTemplate.opsForSet().move(sourceKey, item, destinationKey);
            log.info("sMove(...) => sourceKey -> {},destinationKey -> {},item -> {}",
                    sourceKey, destinationKey, item);
            log.info("sMove(...) =>  result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Gets the number of elements in the set corresponding to the key
         * <p>
         * Note: if the key does not exist, 0 will be returned
         *
         * @param key Locate the key of the set
         * @return (key Number of elements in the corresponding) set
         */
        public static long sSize(String key) {
            log.info("sSize(...) => key -> {}", key);
            Long size = redisTemplate.opsForSet().size(key);
            log.info("sSize(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Judge whether the set (corresponding to the key) contains item
         * <p>
         * Note: if the key does not exist, false is returned
         *
         * @param key  Locate the key of the set
         * @param item Found item
         * @return (key Does the corresponding set contain item
         */
        public static boolean sIsMember(String key, Object item) {
            log.info("sSize(...) => key -> {},size -> {}", key, item);
            Boolean result = redisTemplate.opsForSet().isMember(key, item);
            log.info("sSize(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Get the intersection of two (key corresponding) sets
         * <p>
         * Note: if there is no intersection, an empty set is returned (, not null)
         * Note: if one of the keys does not exist (or both keys do not exist), an empty set is returned (, not null)
         *
         * @param key      Key to locate one of the set s
         * @param otherKey Key to locate the other set
         * @return item intersection
         */
        public static Set<String> sIntersect(String key, String otherKey) {
            log.info("sIntersect(...) => key -> {},otherKey -> {}", key, otherKey);
            Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKey);
            log.info("sIntersect(...) => intersectResult -> {}", intersectResult);
            return intersectResult;
        }

        /**
         * Get the intersection of multiple (key corresponding) sets
         * <p>
         * Note: if there is no intersection, an empty set is returned (, not null)
         * Note: if > = 1 key does not exist, an empty set (instead of null) will be returned
         *
         * @param key       Key to locate one of the set s
         * @param otherKeys Locate keysets of other sets
         * @return item intersection
         */
        public static Set<String> sIntersect(String key, Collection<String> otherKeys) {
            log.info("sIntersect(...) => key -> {},otherKeys -> {}", key, otherKeys);
            Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKeys);
            log.info("sIntersect(...) => intersectResult -> {}", intersectResult);
            return intersectResult;
        }

        /**
         * Get the intersection of two sets (corresponding to the key) and add the result to the Set corresponding to the storeKey
         * <p>
         * case1: If the intersection is not empty and the storeKey does not exist, the corresponding storeKey will be created and the intersection will be added to the set (corresponding to the storeKey)
         * case2: If the intersection is not empty and the storeKey already exists, all items in the original set (corresponding to the storeKey) will be cleared, and then the intersection will be added to the set (corresponding to the storeKey)
         * case3: If the intersection is empty, the following operations will not be performed and 0 will be returned directly
         * <p>
         * Note: see {@ link SetOps#sIntersect(String, String)} for the part of finding intersection
         *
         * @param key      Key to locate one of the set s
         * @param otherKey Key to locate the other set
         * @param storeKey Locate the key of the set to which you want to add the intersection
         * @return add After the set (corresponding to the storeKey) is, the size corresponding to the set
         */
        public static long sIntersectAndStore(String key, String otherKey, String storeKey) {
            log.info("sIntersectAndStore(...) => key -> {},otherKey -> {},storeKey -> {}",
                    key, otherKey, storeKey);
            Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKey, storeKey);
            log.info("sIntersectAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Get the intersection of multiple sets (corresponding to the key) and add the results to the Set corresponding to the storeKey
         * <p>
         * case1: If the intersection is not empty and the storeKey does not exist, the corresponding storeKey will be created and the intersection will be added to the set (corresponding to the storeKey)
         * case2: If the intersection is not empty and the storeKey already exists, all items in the original set (corresponding to the storeKey) will be cleared, and then the intersection will be added to the set (corresponding to the storeKey)
         * case3: If the intersection is empty, the following operations will not be performed and 0 will be returned directly
         * <p>
         * Note: see {@ link SetOps#sIntersect(String, Collection)} for the part of finding intersection
         */
        public static long sIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {
            log.info("sIntersectAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}", key, otherKeys, storeKey);
            Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKeys, storeKey);
            log.info("sIntersectAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Gets the union of two (key corresponding) sets
         * <p>
         * Note: the elements in the union Set are also unique, which is guaranteed by Set
         *
         * @param key      Key to locate one of the set s
         * @param otherKey Key to locate the other set
         * @return item Union
         */
        public static Set<String> sUnion(String key, String otherKey) {
            log.info("sUnion(...) => key -> {},otherKey -> {}", key, otherKey);
            Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKey);
            log.info("sUnion(...) => unionResult -> {}", unionResult);
            return unionResult;
        }

        /**
         * Gets the union of two (key corresponding) sets
         * <p>
         * Note: the elements in the union Set are also unique, which is guaranteed by Set
         *
         * @param key       Key to locate one of the set s
         * @param otherKeys Locate keysets of other sets
         * @return item Union
         */
        public static Set<String> sUnion(String key, Collection<String> otherKeys) {
            log.info("sUnion(...) => key -> {},otherKeys -> {}", key, otherKeys);
            Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKeys);
            log.info("sUnion(...) => unionResult -> {}", unionResult);
            return unionResult;
        }

        /**
         * Obtain the union of two sets (corresponding to the key) and add the result to the Set corresponding to the storeKey
         * <p>
         * case1: If the union is not empty and the storeKey does not exist, the corresponding storeKey will be created and the Union will be added to the set (corresponding to the storeKey)
         * case2: If the union set is not empty and the storeKey already exists, all items in the original set (corresponding to the storeKey) will be cleared, and then the union set will be added to the set (corresponding to the storeKey)
         * case3: If the union is empty, the following operations will not be performed and 0 will be returned directly
         * <p>
         * Note: see {@ link SetOps#sUnion(String, String)} for the part of union set
         *
         * @param key      Key to locate one of the set s
         * @param otherKey Key to locate the other set
         * @param storeKey Locate the key of the set to which you want to add the union
         * @return add After the set (corresponding to the storeKey) is, the size corresponding to the set
         */
        public static long sUnionAndStore(String key, String otherKey, String storeKey) {
            log.info("sUnionAndStore(...) => key -> {},otherKey -> {},storeKey -> {}",
                    key, otherKey, storeKey);
            Long size = redisTemplate.opsForSet().unionAndStore(key, otherKey, storeKey);
            log.info("sUnionAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Obtain the union of two sets (corresponding to the key) and add the result to the Set corresponding to the storeKey
         * <p>
         * case1: If the union is not empty and the storeKey does not exist, the corresponding storeKey will be created and the Union will be added to the set (corresponding to the storeKey)
         * case2: If the union set is not empty and the storeKey already exists, all items in the original set (corresponding to the storeKey) will be cleared, and then the union set will be added to the set (corresponding to the storeKey)
         * case3: If the union is empty, the following operations will not be performed and 0 will be returned directly
         * <p>
         * Note: see {@ link SetOps#sUnion(String, Collection)} for the part of union set
         *
         * @param key       Key to locate one of the set s
         * @param otherKeys Locate keysets of other sets
         * @param storeKey  Locate the key of the set to which you want to add the union
         * @return add After the set (corresponding to the storeKey) is, the size corresponding to the set
         */
        public static long sUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {
            log.info("sUnionAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}",
                    key, otherKeys, storeKey);
            Long size = redisTemplate.opsForSet().unionAndStore(key, otherKeys, storeKey);
            log.info("sUnionAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Get the difference Set of (key corresponding) Set minus (otherKey corresponding) Set
         * <p>
         * Note: if the subtracted key does not exist, the result is an empty set (, not null)
         * Note: if the subtracted key exists but the subtracted key does not exist, the result is Set
         *
         * @param key      Key to locate "subtracted set"
         * @param otherKey Locate the key of "subtraction set"
         * @return item Difference set
         */
        public static Set<String> sDifference(String key, String otherKey) {
            log.info("sDifference(...) => key -> {},otherKey -> {}",
                    key, otherKey);
            Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKey);
            log.info("sDifference(...) => differenceResult -> {}", differenceResult);
            return differenceResult;
        }

        /**
         * Get the difference Set of (key corresponding) Set minus (otherKeys corresponding) Sets
         * <p>
         * Note: if the subtracted key does not exist, the result is an empty set (, not null)
         * Note: if the subtracted key exists but the subtracted key does not exist, the result is Set
         * <p>
         * Tip: when there are multiple subtractions, it doesn't matter which subtraction is subtracted first and which subtraction is subtracted later. It doesn't affect the final result
         *
         * @param key       Key to locate "subtracted set"
         * @param otherKeys Locate the key set of "subtraction sets"
         * @return item Difference set
         */
        public static Set<String> sDifference(String key, Collection<String> otherKeys) {
            log.info("sDifference(...) => key -> {},otherKeys -> {}", key, otherKeys);
            Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKeys);
            log.info("sDifference(...) => differenceResult -> {}", differenceResult);
            return differenceResult;
        }

        /**
         * Get the difference Set of (key corresponding) Set minus (otherKey corresponding) Set, and add the result to the Set corresponding to storeKey
         * <p>
         * case1: If the difference set is not empty and the storeKey does not exist, the corresponding storeKey will be created and the difference set will be added to the set (corresponding to the storeKey)
         * case2: If the difference set is not empty and the storeKey already exists, all items in the original set (corresponding to the storeKey) will be cleared, and then the difference set will be added to the set (corresponding to the storeKey)
         * case3: If the operation is not null, the following difference set is returned directly
         * <p>
         * Note: see {@ link SetOps#sDifference(String, String)} for the part of union set
         *
         * @param key      Key to locate "subtracted set"
         * @param otherKey Locate the key of "subtraction set"
         * @param storeKey Locate the key of the set to which you want to add the difference set
         * @return add After the set (corresponding to the storeKey) is, the size corresponding to the set
         */
        public static long sDifferenceAndStore(String key, String otherKey, String storeKey) {
            log.info("sDifferenceAndStore(...) => key -> {},otherKey -> {},storeKey -> {}",
                    key, otherKey, storeKey);
            Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKey, storeKey);
            log.info("sDifferenceAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Get the difference Set of (key corresponding) Set minus (otherKey corresponding) Set, and add the result to the Set corresponding to storeKey
         * <p>
         * case1: If the difference set is not empty and the storeKey does not exist, the corresponding storeKey will be created and the difference set will be added to the set (corresponding to the storeKey)
         * case2: If the difference set is not empty and the storeKey already exists, all items in the original set (corresponding to the storeKey) will be cleared, and then the difference set will be added to the set (corresponding to the storeKey)
         * case3: If the difference set is empty, the following operations will not be performed and 0 will be returned directly
         * <p>
         * Note: see {@ link SetOps#sDifference(String, String)} for the part of union set
         *
         * @param key       Key to locate "subtracted set"
         * @param otherKeys Locate the key set of "subtraction sets"
         * @param storeKey  Locate the key of the set to which you want to add the difference set
         * @return add After the set (corresponding to the storeKey) is, the size corresponding to the set
         */
        public static long sDifferenceAndStore(String key, Collection<String> otherKeys, String storeKey) {
            log.info("sDifferenceAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}",
                    key, otherKeys, storeKey);
            Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKeys, storeKey);
            log.info("sDifferenceAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Get the set corresponding to the key
         * <p>
         * Note: if the key does not exist, an empty set(, not null) will be returned
         *
         * @param key Locate the key of the set
         * @return (key Corresponding) set
         */
        public static Set<String> sMembers(String key) {
            log.info("sMembers(...) => key -> {}", key);
            Set<String> members = redisTemplate.opsForSet().members(key);
            log.info("sMembers(...) => members -> {}", members);
            return members;
        }

        /**
         * Randomly obtain an item from the set corresponding to the key
         *
         * @param key Locate the key of the set
         * @return Randomly acquired items
         */
        public static String sRandomMember(String key) {
            log.info("sRandomMember(...) => key -> {}", key);
            String randomItem = redisTemplate.opsForSet().randomMember(key);
            log.info("sRandomMember(...) => randomItem -> {}", randomItem);
            return randomItem;
        }

        /**
         * Obtain the random item of count times from the set corresponding to the key (the same item in the set may be obtained multiple times)
         * <p>
         * Note: count can be greater than the size of set
         * Note: the same value may exist in the obtained results
         *
         * @param key   Locate the key of the set
         * @param count How many items do you want to take
         * @return Randomly acquired itemset
         */
        public static List<String> sRandomMembers(String key, long count) {
            log.info("sRandomMembers(...) => key -> {},count -> {}", key, count);
            List<String> randomItems = redisTemplate.opsForSet().randomMembers(key, count);
            log.info("sRandomMembers(...) => randomItems -> {}", randomItems);
            return randomItems;
        }

        /**
         * Randomly obtain count items from the set corresponding to the key
         * <p>
         * Note: if count > = size of set, the returned is the set corresponding to this key
         * Note: there are no duplicate items in the extracted results
         *
         * @param key   Locate the key of the set
         * @param count How many items do you want to take
         * @return Randomly acquired itemset
         */
        public static Set<String> sDistinctRandomMembers(String key, long count) {
            log.info("sDistinctRandomMembers(...) => key -> {},count -> {}", key, count);
            Set<String> distinctRandomItems = redisTemplate.opsForSet().distinctRandomMembers(key, count);
            log.info("sDistinctRandomMembers(...) => distinctRandomItems -> {}", distinctRandomItems);
            return distinctRandomItems;
        }

        /**
         * Match the corresponding item in the (key corresponding) set according to the options, and return the corresponding item set
         * <p>
         * <p>
         * Note: the creation method of ScanOptions instance is as follows:
         * 1,ScanOptions.NONE
         * 2,ScanOptions.scanOptions().match("n??e").build()
         *
         * @param key     Locate the key of the set
         * @param options Conditions for matching item s in set
         *                Note: scanoptions None means all matches
         *                Note: scanoptions scanOptions(). match(pattern). Build () means matching according to pattern,
         *                Where the wildcard * can be used in pattern? Wait,
         *                * Indicates > = 0 characters
         *                ? Indicates that there is and only one character
         *                The matching rule here is the same as that at {@ link KeyOps#keys(String)}
         * @return Items in the matched (key corresponding) set
         */
        @SneakyThrows
        public static Cursor<String> sScan(String key, ScanOptions options) {
            log.info("sScan(...) => key -> {},options -> {}", key, mapper.writeValueAsString(options));
            Cursor<String> cursor = redisTemplate.opsForSet().scan(key, options);
            log.info("sScan(...) => cursor -> {}", mapper.writeValueAsString(cursor));
            return cursor;
        }
    }

    /**
     * ZSet Related operation
     * <p>
     * Special note: ZSet is ordered,
     * It is not only reflected in the orderly storage in redis
     * It is also reflected in: the return value type in this tool class ZSetOps is set <? > The actual return type of the method is linkedhashset <? >
     * <p>
     * Tip: ZSet in redis is equal to the combination of set in redis and Hash in redis to some extent
     * Tip: for the data structure of String in redis, please refer to the data structure of resources / data structure / Zset (example 1) png
     * redis For the data structure of String in, please refer to the data structure of resources / data structure / Zset (example 2) png
     * Tip: the entryKey in ZSet is the member item, and the entryValue is the score of this member item. ZSet sorts the heap members according to the score of the members
     */
    public static class ZSetOps {

        /**
         * Add (item,score) to zset corresponding to (key)
         * <p>
         * Note: item is the entryKey member item and score is the entryValue score
         * <p>
         * Note: if the same item (as the item to be added this time) already exists in the zset (corresponding to the key), the addition operation will fail and return false;
         * But!!! The score of the original item in zset will be updated to the score of the same item in this add
         * Therefore, you can also update the score corresponding to the item through zAdd
         * <p>
         * Note: score can be positive, negative or 0; In short, it can be in the range of double
         * <p>
         * Note: if the value of score is the same, it will be sorted by item
         *
         * @param key   Locate the key of the set
         * @param item  Member item to be added to zset (corresponding to key)
         * @param score item Score of
         * @return Add successfully
         */
        public static boolean zAdd(String key, String item, double score) {
            log.info("zAdd(...) => key -> {},item -> {},score -> {}", key, item, score);
            Boolean result = redisTemplate.opsForZSet().add(key, item, score);
            log.info("zAdd(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Batch add entry < item, score >
         * <p>
         * Note: if there are items with the same item in the entry < item, score > set (, score is different), redis will delete them before performing the real batch add operation
         * Filter out one of the item s
         * Note: similarly, if the same item (as the item to be added this time) already exists in the zset (corresponding to the key), then in this batch addition operation,
         * Adding 1 to the counter will fail or not; But!!! The score of the original item in zset will be updated to this
         * The score of the same item in the secondary add. Therefore, you can also update the score corresponding to the item through zAdd
         *
         * @param key     Locate the key of the set
         * @param entries Entry < item, score > set to add
         * @return The number of entries added to the (key corresponding) zset this time
         */
        @SneakyThrows
        public static long zAdd(String key, Set<TypedTuple<String>> entries) {
            log.info("zAdd(...) => key -> {},entries -> {}", key, mapper.writeValueAsString(entries));
            Long count = redisTemplate.opsForZSet().add(key, entries);
            log.info("zAdd(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Remove item from zset (corresponding to key)
         * <p>
         * Note: if the key does not exist, 0 will be returned
         *
         * @param key   Locate the key of the set
         * @param items Itemset to remove
         * @return Number of items actually removed
         */
        public static long zRemove(String key, Object... items) {
            log.info("zRemove(...) => key -> {},items -> {}", key, items);
            Long count = redisTemplate.opsForZSet().remove(key, items);
            log.info("zRemove(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Remove the item s in the zset (corresponding to the key) whose ranking range is within [startIndex,endIndex]
         * <p>
         * Note: by default, press score Items are ranked in ascending order, starting from 0
         * <p>
         * Note: similar to the index in List, ranking can be divided into several ways:
         * Ranking from front to back (positive): 0, 1, 2
         * Ranking from bottom to top (reverse): - 1, - 2, - 3
         * <p>
         * Note: whether using forward ranking or reverse ranking, when using this method, ensure the position of the element represented by startRange
         * In front of the position of the element represented by endRange, such as:
         * Example 1: redisutil ZSetOps. zRemoveRange("name",0,2);
         * Example 2: redisutil ZSetOps. zRemoveRange("site",-2,-1);
         * Example 3: redisutil ZSetOps. zRemoveRange("foo",0,-1);
         * <p>
         * Note: if the key does not exist, 0 will be returned
         *
         * @param key        Locate the key of the set
         * @param startRange Ranking of start items
         * @param endRange   Ranking of ending items
         * @return Number of items actually removed
         */
        public static long zRemoveRange(String key, long startRange, long endRange) {
            log.info("zRemoveRange(...) => key -> {},startRange -> {},endRange -> {}",
                    key, startRange, endRange);
            Long count = redisTemplate.opsForZSet().removeRange(key, startRange, endRange);
            log.info("zRemoveRange(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Remove the item s in the zset (corresponding to the key) whose score range is within [minScore,maxScore]
         * <p>
         * Tip: Although the deletion range includes endpoints on both sides (i.e. minScore and maxScore), due to the accuracy problem of double, it is recommended:
         * When setting the value, minScore should be set a little smaller than the smallest score of the items to be deleted
         * maxScore It should be set a little larger than the maximum score of the items to be deleted
         * Note: I have simply tested several groups of data, and there is no accuracy problem yet
         * <p>
         * Note: if the key does not exist, 0 will be returned
         *
         * @param key      Locate the key of the set
         * @param minScore score Lower limit (including this value)
         * @param maxScore score Upper limit (including this value)
         * @return Number of items actually removed
         */
        public static long zRemoveRangeByScore(String key, double minScore, double maxScore) {
            log.info("zRemoveRangeByScore(...) => key -> {},startIndex -> {},startIndex -> {}",
                    key, minScore, maxScore);
            Long count = redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
            log.info("zRemoveRangeByScore(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Increase / decrease (in the zset corresponding to the key), the score value of item
         *
         * @param key   Locate the key of zset
         * @param item  term
         * @param delta Variation (positive increase, negative decrease)
         * @return Modified score value
         */
        public static double zIncrementScore(String key, String item, double delta) {
            log.info("zIncrementScore(...) => key -> {},item -> {},delta -> {}", key, item, delta);
            Double scoreValue = redisTemplate.opsForZSet().incrementScore(key, item, delta);
            log.info("zIncrementScore(...) => scoreValue -> {}", scoreValue);
            if (scoreValue == null) {
                throw new RedisOpsResultIsNullException();
            }
            return scoreValue;
        }

        /**
         * Returns the ranking of item in zset (corresponding to key) (from small to large by score)
         * <p>
         * Note: ranking from 0 means that this method is equivalent to returning the position index of item in zset (corresponding to key)
         * Note: if the key or item does not exist, null is returned
         * Note: the sorting rule is score,item, that is, score is the first priority, and if the scores are the same, then item
         *
         * @param key  Locate the key of zset
         * @param item term
         * @return Ranking (equivalent to: index)
         */
        public static long zRank(String key, Object item) {
            log.info("zRank(...) => key -> {},item -> {}", key, item);
            Long rank = redisTemplate.opsForZSet().rank(key, item);
            log.info("zRank(...) => rank -> {}", rank);
            if (rank == null) {
                throw new RedisOpsResultIsNullException();
            }
            return rank;
        }

        /**
         * Returns the ranking of item in zset (corresponding to key) (from large to small by score)
         * <p>
         * Note: the ranking starts from 0. Supplement: because it is sorted by score from large to small, the item corresponding to the maximum score is ranked as 0
         * Note: if the key or item does not exist, null is returned
         * Note: the sorting rule is score,item, that is, score is the first priority, and if the scores are the same, then item
         *
         * @param key  Locate the key of zset
         * @param item term
         * @return Ranking (equivalent to: index)
         */
        public static long zReverseRank(String key, Object item) {
            log.info("zReverseRank(...) => key -> {},item -> {}", key, item);
            Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, item);
            log.info("zReverseRank(...) => reverseRank -> {}", reverseRank);
            if (reverseRank == null) {
                throw new RedisOpsResultIsNullException();
            }
            return reverseRank;
        }

        /**
         * According to the index position, get the item item set ranked in [start,end] in zset (corresponding to key)
         * <p>
         * Note: whether using forward ranking or reverse ranking, when using this method, ensure that the element represented by startIndex is correct
         * The position is in front of the position of the element represented by endIndex, such as:
         * Example 1: redisutil ZSetOps. zRange("name",0,2);
         * Example: redistil ZSetOps. zRange("site",-2,-1);
         * Example 3: redisutil ZSetOps. zRange("foo",0,-1);
         * <p>
         * Note: if the key does not exist, an empty set will be returned
         * <p>
         * Note: when the range of [start,end] is larger than the range of the actual zset, the item set corresponding to the "intersection" on the range is returned
         *
         * @param key   Locate the key of zset
         * @param start Ranking start position
         * @param end   Ranking end position
         * @return Corresponding item itemset
         */
        public static Set<String> zRange(String key, long start, long end) {
            log.info("zRange(...) => key -> {},start -> {},end -> {}", key, start, end);
            Set<String> result = redisTemplate.opsForZSet().range(key, start, end);
            log.info("zRange(...) => result -> {}", result);
            return result;
        }

        /**
         * Get all item items in zset (corresponding to key)
         *
         * @param key Key to locate zset
         * @return (key All item items in the corresponding) zset
         * @see ZSetOps#zRange(String, long, long)
         */
        public static Set<String> zWholeZSetItem(String key) {
            log.info("zWholeZSetItem(...) => key -> {}", key);
            Set<String> result = redisTemplate.opsForZSet().range(key, 0, -1);
            log.info("zWholeZSetItem(...) =>result -> {}", result);
            return result;
        }

        /**
         * According to the index position, obtain the entry set ranked in [start,end] in the zset (corresponding to the key)
         * <p>
         * Note: whether using forward ranking or reverse ranking, when using this method, ensure that the element represented by startIndex is correct
         * The position is in front of the position of the element represented by endIndex, such as:
         * Example 1: redisutil ZSetOps. zRange("name",0,2);
         * Example 2: redisutil ZSetOps. zRange("site",-2,-1);
         * Example 3: redisutil ZSetOps. zRange("foo",0,-1);
         * <p>
         * Note: if the key does not exist, an empty set will be returned
         * <p>
         * Note: when the range of [start,end] is larger than the range of the actual zset, the item set corresponding to the "intersection" on the range is returned
         * <p>
         * Note: this method is similar to {@ link ZSetOps#zRange(String, long, long)}, but it returns the entry set instead of the item set
         *
         * @param key   Locate the key of zset
         * @param start Ranking start position
         * @param end   Ranking end position
         * @return Corresponding entry set
         */
        @SneakyThrows
        public static Set<TypedTuple<String>> zRangeWithScores(String key, long start, long end) {
            log.info("zRangeWithScores(...) => key -> {},start -> {},end -> {}", key, start, end);
            Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
            log.info("zRangeWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
            return entries;
        }

        /**
         * Get all entries in zset (corresponding to key)
         *
         * @param key Key to locate zset
         * @return (key All entries in the corresponding) zset
         * @see ZSetOps#zRangeWithScores(String, long, long)
         */
        @SneakyThrows
        public static Set<TypedTuple<String>> zWholeZSetEntry(String key) {
            log.info("zWholeZSetEntry(...) => key -> {}", key);
            Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
            log.info("zWholeZSetEntry(...) => entries -> {}", key, mapper.writeValueAsString(entries));
            return entries;
        }

        /**
         * According to the score, obtain the item itemset whose score value in zset (corresponding to the key) is in [minScore,maxScore]
         * <p>
         * Note: if the key does not exist, an empty set will be returned
         * Note: when the range of [minScore,maxScore] is larger than the range of score in the actual zset, the item set corresponding to the "intersection" on the range is returned
         * <p>
         * Tip: Although the deletion range includes endpoints on both sides (i.e. minScore and maxScore), due to the accuracy problem of double, it is recommended:
         * When setting the value, minScore should be set a little smaller than the smallest score of the items to be deleted
         * maxScore It should be set a little larger than the maximum score of the items to be deleted
         * Note: I have simply tested several groups of data, and there is no accuracy problem yet
         *
         * @param key      Locate the key of zset
         * @param minScore score lower limit
         * @param maxScore score upper limit
         * @return Corresponding item itemset
         */
        public static Set<String> zRangeByScore(String key, double minScore, double maxScore) {
            log.info("zRangeByScore(...) => key -> {},minScore -> {},maxScore -> {}", key, minScore, maxScore);
            Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
            log.info("zRangeByScore(...) => items -> {}", items);
            return items;
        }

        /**
         * According to the score, obtain the score value in zset (corresponding to the key) in [minScore,maxScore], and the score is in [minScore,
         * count item items whose ranking is greater than or equal to offset
         * <p>
         * Special note: for those who are not particularly familiar with redis, it is best to use positive numbers for offset and count to avoid ambiguity in understanding
         * <p>
         * Note: if the key does not exist, an empty set will be returned
         * <p>
         * Tip: Although the deletion range includes endpoints on both sides (i.e. minScore and maxScore), due to the accuracy problem of double, it is recommended:
         * When setting the value, minScore should be set a little smaller than the smallest score of the items to be deleted
         * maxScore It should be set a little larger than the maximum score of the items to be deleted
         * Note: I have simply tested several groups of data, and there is no accuracy problem yet
         *
         * @param key      Locate the key of zset
         * @param minScore score lower limit
         * @param maxScore score upper limit
         * @param offset   Offset (i.e. lower ranking limit)
         * @param count    Number of elements expected to be obtained
         * @return Corresponding item itemset
         */
        public static Set<String> zRangeByScore(String key, double minScore, double maxScore,
                                                long offset, long count) {
            log.info("zRangeByScore(...) => key -> {},minScore -> {},maxScore -> {},offset -> {},"
                    + "count -> {}", key, minScore, maxScore, offset, count);
            Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);
            log.info("zRangeByScore(...) => items -> {}", items);
            return items;
        }

        /**
         * Get the entry in [minScore,maxScore] for all scores in zset (corresponding to key)
         *
         * @param key      Key to locate zset
         * @param minScore score lower limit
         * @param maxScore score upper limit
         * @return (key All scores in the corresponding) zset are in the entry in [minScore, maxScore]
         * @see ZSetOps#zRangeByScore(String, double, double)
         * <p>
         * Note: if the key does not exist, an empty set will be returned
         * Note: when the range of [minScore,maxScore] is larger than the range of score in the actual zset, the item set corresponding to the "intersection" on the range is returned
         */
        @SneakyThrows
        public static Set<TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore, double maxScore) {
            log.info("zRangeByScoreWithScores(...) => key -> {},minScore -> {},maxScore -> {}",
                    key, minScore, maxScore);
            Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore, maxScore);
            log.info("zRangeByScoreWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
            return entries;
        }

        /**
         * Get the count entries whose score is in [minScore,maxScore] and whose ranking is greater than or equal to offset in the zset (corresponding to the key)
         * <p>
         * Special note: for those who are not particularly familiar with redis, it is best to use positive numbers for offset and count to avoid ambiguity in understanding
         *
         * @param key      Key to locate zset
         * @param minScore score lower limit
         * @param maxScore score upper limit
         * @param offset   Offset (i.e. lower ranking limit)
         * @param count    Number of elements expected to be obtained
         * @return [startIndex, endIndex] & [minScore,maxScore]entry in
         */
        @SneakyThrows
        public static Set<TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore,
                                                                      double maxScore, long offset,
                                                                      long count) {
            log.info("zRangeByScoreWithScores(...) => key -> {},minScore -> {},maxScore -> {},"
                            + " offset -> {},count -> {}",
                    key, minScore, maxScore, offset, count);
            Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore,
                    maxScore, offset, count);
            log.info("zRangeByScoreWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
            return entries;
        }


        /**
         * When obtaining, first reverse the order of score, and then obtain the item set ranked in [start,end] in zset (corresponding to key) according to the index position
         *
         * @see ZSetOps#zRange(String, long, long) It's just that zReverseRange will be in reverse order in advance
         */
        public static Set<String> zReverseRange(String key, long start, long end) {
            log.info("zReverseRange(...) => key -> {},start -> {},end -> {}", key, start, end);
            Set<String> entries = redisTemplate.opsForZSet().reverseRange(key, start, end);
            log.info("zReverseRange(...) => entries -> {}", entries);
            return entries;
        }

        /**
         * When obtaining, first reverse the score, and then obtain the entry set ranked in [start,end] in zset (corresponding to key) according to the index position
         *
         * @see ZSetOps#zRangeWithScores(String, long, long) It's just that zReverseRangeWithScores will be in reverse order in advance
         */
        @SneakyThrows
        public static Set<TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {
            log.info("zReverseRangeWithScores(...) => key -> {},start -> {},end -> {}", key, start, end);
            Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
            log.info("zReverseRangeWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
            return entries;
        }

        /**
         * When obtaining, first reverse the order of score, and then obtain the item itemset whose score value in zset (corresponding to key) is in [minScore,maxScore] according to score
         *
         * @see ZSetOps#zRangeByScore(String, double, double) It's just that zverserangebyscore will be in reverse order in advance
         */
        public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore) {
            log.info("zReverseRangeByScore(...) => key -> {},minScore -> {},maxScore -> {}",
                    key, minScore, maxScore);
            Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);
            log.info("zReverseRangeByScore(...) => items -> {}", items);
            return items;
        }

        /**
         * When obtaining, first reverse the order of scores, and then obtain the entries in [minScore,maxScore] for all scores in zset (corresponding to key)
         *
         * @see ZSetOps#zRangeByScoreWithScores(String, double, double) It's just that zReverseRangeByScoreWithScores will be in reverse order in advance
         */
        @SneakyThrows
        public static Set<TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double minScore, double maxScore) {
            log.info("zReverseRangeByScoreWithScores(...) => key -> {},minScore -> {},maxScore -> {}",
                    key, minScore, maxScore);
            Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
                    minScore, maxScore);
            log.info("zReverseRangeByScoreWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
            return entries;
        }

        /**
         * When obtaining, first reverse the score, and then according to the score, obtain the score value of zset (corresponding to key) in [minScore,maxScore],
         * score count item items in [minScore, ranking greater than or equal to offset
         *
         * @see ZSetOps#zRangeByScore(String, double, double, long, long) It's just that zverserangebyscore will be in reverse order in advance
         */
        public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
            log.info("zReverseRangeByScore(...) => key -> {},minScore -> {},maxScore -> {},offset -> {},"
                    + "count -> {}", key, minScore, maxScore, offset, count);
            Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);
            log.info("items -> {}", items);
            return items;
        }

        /**
         * Count the number of item s (in the zset corresponding to the key) whose score is in [minScore,maxScore]
         *
         * @param key      Locate the key of zset
         * @param minScore score lower limit
         * @param maxScore score upper limit
         * @return [minScore, maxScore]Number of item s in
         */
        public static long zCount(String key, double minScore, double maxScore) {
            log.info("zCount(...) => key -> {},minScore -> {},maxScore -> {}", key, minScore, maxScore);
            Long count = redisTemplate.opsForZSet().count(key, minScore, maxScore);
            log.info("zCount(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * Count the number of item s in zset (corresponding to key)
         * <p>
         * Note: this method is equivalent to {@ link ZSetOps#zZCard(String)}
         *
         * @param key Locate the key of zset
         * @return zset Number of item s in
         */
        public static long zSize(String key) {
            log.info("zSize(...) => key -> {}", key);
            Long size = redisTemplate.opsForZSet().size(key);
            log.info("zSize(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Count the number of item s in zset (corresponding to key)
         * <p>
         * Note: this method is equivalent to {@ link ZSetOps#zSize(String)}
         *
         * @param key Locate the key of zset
         * @return zset Number of item s in
         */
        public static long zZCard(String key) {
            log.info("zZCard(...) => key -> {}", key);
            Long size = redisTemplate.opsForZSet().zCard(key);
            log.info("zZCard(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Count the score of the specified item in the zset (corresponding to the key)
         *
         * @param key  Locate the key of zset
         * @param item zset item in
         * @return item score of
         */
        public static double zScore(String key, Object item) {
            log.info("zScore(...) => key -> {},item -> {}", key, item);
            Double score = redisTemplate.opsForZSet().score(key, item);
            log.info("zScore(...) => score -> {}", score);
            if (score == null) {
                throw new RedisOpsResultIsNullException();
            }
            return score;
        }

        /**
         * Obtain the union of two zsets (corresponding to the key) and add the result to the ZSet corresponding to the storeKey
         * <p>
         * Note: like set, the item in zset is unique. When multiple zsets are in Union, when processing the same item, the value of score will become the sum of the corresponding scores, such as:
         * RedisUtil.ZSetOps.zAdd("name1","a",1);And redisutil ZSetOps. zAdd("name2","a",2);
         * After zUnionAndStore for zset (corresponding to name1 and name2), the corresponding score value of item a in the new zset is 3
         * <p>
         * case1: If the intersection is not empty and the storeKey does not exist, the corresponding storeKey will be created and the Union will be added to the ZSet (corresponding to the storeKey)
         * case2: If the intersection is not empty and the storeKey already exists, all items in the original ZSet (corresponding to the storeKey) will be cleared, and then the Union will be added to the ZSet (corresponding to the storeKey)
         * case3: If the intersection is empty, the following operations will not be performed and 0 will be returned directly
         *
         * @param key      Key to locate one of the zset s
         * @param otherKey Key to locate another zset
         * @param storeKey Locate the key of the set to which you want to add the intersection
         * @return add After accessing the ZSet (corresponding to storeKey), the corresponding size of the ZSet
         */
        public static long zUnionAndStore(String key, String otherKey, String storeKey) {
            log.info("zUnionAndStore(...) => key -> {},otherKey -> {},storeKey -> {}", key, otherKey, storeKey);
            Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKey, storeKey);
            log.info("zUnionAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Obtain the union of two zsets (corresponding to the key) and add the result to the ZSet corresponding to the storeKey
         * <p>
         * Note: like set, the item in zset is unique. When multiple zsets are in Union, when processing the same item, the value of score will become the sum of the corresponding scores, such as:
         * RedisUtil.ZSetOps.zAdd("name1","a",1);And redisutil ZSetOps. zAdd("name2","a",2);
         * After zUnionAndStore for zset (corresponding to name1 and name2), the corresponding score value of item a in the new zset is 3
         * <p>
         * case1: If the union is not empty and the storeKey does not exist, the corresponding storeKey will be created and the Union will be added to the ZSet (corresponding to the storeKey)
         * case2: If the union is not empty and the storeKey already exists, all items in the original ZSet (corresponding to the storeKey) will be cleared, and then the Union will be added to the ZSet (corresponding to the storeKey)
         * case3: If the union is empty, the following operations will not be performed and 0 will be returned directly
         *
         * @param key       Key to locate one of the set s
         * @param otherKeys Locate keysets of other sets
         * @param storeKey  Locate the key of the set to which you want to add the union
         * @return add After accessing the ZSet (corresponding to storeKey), the corresponding size of the ZSet
         */
        public static long zUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {
            log.info("zUnionAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}", key, otherKeys, storeKey);
            Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKeys, storeKey);
            log.info("zUnionAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Get the intersection of two zsets (corresponding to the key) and add the result to the ZSet corresponding to the storeKey
         * <p>
         * Note: like set, item in zset is unique. When multiple zsets Intersect, when processing the same item, the value of score will become the sum of corresponding scores, such as:
         * RedisUtil.ZSetOps.zAdd("name1","a",1);
         * RedisUtil.ZSetOps.zAdd("name1","b",100);
         * And R
         * edisUtil.ZSetOps.zAdd("name2","a",2);
         * edisUtil.ZSetOps.zAdd("name2","c",200);
         * After zIntersectAndStore on zset (corresponding to name1 and name2), the corresponding score value of item a in the new zset is 3
         * <p>
         * case1: If the intersection is not empty and the storeKey does not exist, the corresponding storeKey will be created and the intersection will be added to the ZSet (corresponding to the storeKey)
         * case2: If the intersection is not empty and the storeKey already exists, all items in the original ZSet (corresponding to the storeKey) will be cleared, and then the intersection will be added to the ZSet (corresponding to the storeKey)
         * case3: If the intersection is empty, the following operations will not be performed and 0 will be returned directly
         *
         * @param key      Locate the key for one of the zsets
         * @param otherKey Locate the key of the other ZSet
         * @param storeKey Locate the key of ZSet (to which intersection to add)
         * @return add After accessing the ZSet (corresponding to storeKey), the corresponding size of the ZSet
         */
        public static long zIntersectAndStore(String key, String otherKey, String storeKey) {
            log.info("zIntersectAndStore(...) => key -> {},otherKey -> {},storeKey -> {}", key, otherKey, storeKey);
            Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKey, storeKey);
            log.info("zIntersectAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }

        /**
         * Obtain the intersection of multiple zsets (corresponding to the key) and add the result to the ZSet corresponding to the storeKey
         * <p>
         * case1: If the intersection is not empty and the storeKey does not exist, the corresponding storeKey will be created and the intersection will be added to the ZSet (corresponding to the storeKey)
         * case2: If the intersection is not empty and the storeKey already exists, all items in the original ZSet (corresponding to the storeKey) will be cleared, and then the intersection will be added to the ZSet (corresponding to the storeKey)
         * case3: If the intersection is empty, the following operations will not be performed and 0 will be returned directly
         *
         * @param key       Key to locate one of the set s
         * @param otherKeys Locate keysets of other sets
         * @param storeKey  Locate the key of the set to which you want to add the union
         * @return add After accessing the ZSet (corresponding to storeKey), the corresponding size of the ZSet
         */
        public static long zIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {
            log.info("zIntersectAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}",
                    key, otherKeys, storeKey);
            Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, storeKey);
            log.info("zIntersectAndStore(...) => size -> {}", size);
            if (size == null) {
                throw new RedisOpsResultIsNullException();
            }
            return size;
        }
    }

    /**
     * redis Distributed lock (stand-alone version)
     * <p>
     * Usage (example):
     * boolean flag = false;
     * String lockName = "sichuan:mianyang:fucheng:ds";
     * String lockValue = UUID.randomUUID().toString();
     * try {
     * //	Non blocking acquisition (the maximum lifetime of the lock adopts the default value)
     * flag = RedisUtil.LockOps.getLock(lockName,lockValue);
     * //	Non blocking access to e.g
     * flag = RedisUtil.LockOps.getLock(lockName,lockValue,3,TimeUnit.SECONDS);
     * // Block acquisition (the maximum lifetime of the lock adopts the default value)
     * flag = RedisUtil.LockOps.getLockUntilTimeout(lockName,lockValue,2000);
     * // Blocking access to e.g
     * flag = RedisUtil.LockOps.getLockUntilTimeout(lockName,lockValue,2,TimeUnit.SECONDS,2000);
     * if (!flag) {
     * throw new RuntimeException(" obtain redis-lock[" + lockName + "] fail");
     * }
     * // your logic
     * //	...
     * } finally {
     * if (flag) {
     * RedisUtil.LockOps.releaseLock(lockName,lockValue);
     * }
     * }
     * <p>
     * |--------------------------------------------------------------------------------------------------------------------|
     * |Single machine distributed lock and cluster distributed lock. Special instructions:|
     * |   - This lock is a distributed lock for single Redis|
     * |   - For Redis cluster, the lock may fail. Consider the following situations:|
     * |         First, when client A obtains A lock on the Master through key value (assuming that the key name is key123)|
     * |         Then, when the Master tries to synchronize the data to the Slave, it suddenly hangs up (there is no key123 of the distributed lock on the Slave at this time)|
     * |         Then Slave becomes Master|
     * |         Unfortunately, client B also uses the same key to obtain the distributed lock at this time|
     * |                 Because there is no distributed lock represented by key123 on the current Master|
     * |                 Therefore, when client B obtains the distributed lock through key123|
     * |                 You can succeed|
     * |         At this time, client A and client B acquire the same distributed lock at the same time, and the distributed lock fails|
     * |   - In Redis cluster mode, if strict distributed locks are required, Redlock algorithm can be used to realize the principle of Redlock algorithm:|
     * |     - Get distributed lock:|
     * |           1. The client obtains the current time t0 of the server|
     * |           2. Use the same key and value to obtain locks from five instances in turn|
     * |              Note: in order to avoid taking too long in a redis node and affecting the acquisition of locks of subsequent redis nodes|
     * |                 When acquiring the lock of each Redis node, the client needs to set a small timeout waiting for acquiring the lock|
     * |                 Once the time for obtaining distributed locks at a node exceeds the timeout, it is considered that obtaining distributed locks at this node has failed|
     * |                 (Instead of wasting time on this node), continue to obtain the distributed lock of the next node|
     * |           3. The client subtracts t0 from the current time (t1) to calculate the total time T2 (Note: t2=t1-t0) spent acquiring locks (from all redis nodes)|
     * |              Only t2 is less than the locking time of the lock itself (Note: if the locking time of the lock is 1 hour, assuming that the lock starts at 1 p.m., the lock will be at 2 p.m|
     * |              When it fails, but you don't get the lock until after two o'clock. It's meaningless at this time). Moreover, the client is at least in Redis|
     * |              Only when the lock is obtained on the node can we think that the distributed lock is obtained successfully|
     * |           5. If the lock has been acquired, the actual effective length of the lock = the total effective length of the lock - the length of time consumed to acquire the distributed lock; The actual effective length of the lock shall be > 0|
     * |              Note: that is, if the lock acquisition fails, then|
     * |                  A. It may be the number of locks obtained, which does not meet most principles|
     * |                  B. It may also be that the actual effective length of the lock is not greater than 0|
     * |      - Release the distributed lock: try to delete the lock on each redis node (no matter whether the lock is obtained on the node or not)|
     * |   - For distributed locks under clusters, you can directly use the existing class library < a href=“ https://github.com/redisson/redisson "/>                                |
     * |                                                                                                                    |
     * |   Note: if the Redis cluster project can tolerate the failure of the single version distributed lock caused by the downtime of the master, the single version distributed lock is directly used in the Redis cluster project|
     * |       If the Redis cluster project cannot tolerate the failure of the single version distributed lock, please use the cluster version distributed lock based on the RedLock algorithm|
     * |--------------------------------------------------------------------------------------------------------------------|
     */
    public static class LockOps {

        /**
         * lua Script to ensure the atomicity of the release lock script (to avoid releasing someone else's lock in a concurrent scenario)
         */
        private static final String RELEASE_LOCK_LUA;

        /**
         * Default (maximum) lifetime of distributed locks
         */
        public static final long DEFAULT_LOCK_TIMEOUT = 3;

        /**
         * DEFAULT_LOCK_TIMEOUT Unit of
         */
        public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;

        static {
            // Whether 0 in lua represents failure or not; For java Boolean, if it returns 0, it will be resolved to false
            RELEASE_LOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] "
                    + "then "
                    + "    return redis.call('del',KEYS[1]) "
                    + "else "
                    + "    return 0 "
                    + "end ";
        }

        /**
         * Acquire (distributed) locks
         * <p>
         * Note: the obtained results are returned immediately and non blocking
         *
         * @see LockOps#getLock(String, String, long, TimeUnit)
         */
        public static boolean getLock(final String key, final String value) {
            return getLock(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT);
        }

        /**
         * Acquire (distributed) locks
         * If successful, return directly;
         * If it fails, retry until it succeeds or times out
         * <p>
         * Note: the acquisition result is blocked. It is returned only after success or timeout
         *
         * @param retryTimeoutLimit Timeout length of retry (ms)
         *                          For other parameters, see:
         * @return Is it successful
         * @see LockOps#getLock(String, String, long, TimeUnit)
         */
        public static boolean getLockUntilTimeout(final String key, final String value,
                                                  final long retryTimeoutLimit) {
            return getLockUntilTimeout(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT, retryTimeoutLimit);
        }

        /**
         * Acquire (distributed) locks
         * If successful, return directly;
         * If it fails, retry until it succeeds or times out
         * <p>
         * Note: the acquisition result is blocked. It is returned only after success or timeout
         *
         * @param retryTimeoutLimit Timeout length of retry (ms)
         *                          For other parameters, see:
         * @return Is it successful
         * @see LockOps#getLock(String, String, long, TimeUnit, boolean)
         */
        public static boolean getLockUntilTimeout(final String key, final String value,
                                                  final long timeout, final TimeUnit unit,
                                                  final long retryTimeoutLimit) {
            log.info("getLockUntilTimeout(...) => key -> {},value -> {},timeout -> {},unit -> {},"
                    + "retryTimeoutLimit -> {}ms", key, value, timeout, unit, retryTimeoutLimit);
            long startTime = Instant.now().toEpochMilli();
            long now = startTime;
            do {
                try {
                    boolean alreadyGotLock = getLock(key, value, timeout, unit, false);
                    if (alreadyGotLock) {
                        log.info("getLockUntilTimeout(...) => consume time -> {}ms,result -> true", now - startTime);
                        return true;
                    }
                } catch (Exception e) {
                    log.warn("getLockUntilTimeout(...) => try to get lock failure! e.getMessage -> {}",
                            e.getMessage());
                }
                now = Instant.now().toEpochMilli();
            } while (now < startTime + retryTimeoutLimit);
            log.info("getLockUntilTimeout(...) => consume time -> {}ms,result -> false", now - startTime);
            return false;
        }

        /**
         * Acquire (distributed) locks
         * <p>
         * Note: the obtained results are returned immediately and non blocking
         *
         * @see LockOps#getLock(String, String, long, TimeUnit, boolean)
         */
        public static boolean getLock(final String key, final String value,
                                      final long timeout, final TimeUnit unit) {
            return getLock(key, value, timeout, unit, true);
        }

        /**
         * Acquire (distributed) locks
         * <p>
         * Note: the obtained results are returned immediately and non blocking
         *
         * @param key       Lock name
         * @param value     value corresponding to lock name
         *                  Note: value generally adopts globally unique values, such as requestId, uuid, etc
         *                  In this way, the value value can be verified again when the lock is released,
         *                  Ensure that the lock on yourself can only be released by yourself and will not be released by others
         *                  Of course, if the lock expires, it will be automatically deleted and released by redis
         * @param timeout   The (maximum) lifetime of the lock
         *                  Note: generally, the acquisition lock and release lock are used in pairs. Before the lock reaches the (maximum) survival time, it will be released actively
         *                  However, in some cases (for example, after the program obtains the lock and before releasing the lock, it breaks), the lock cannot be released, so it needs to wait for the lock to pass
         *                  After the (maximum) survival time is, it is automatically deleted and cleaned up by redis, so as to ensure that no dead data is left in redis
         * @param unit      timeout Unit of
         * @param recordLog Log
         * @return Is it successful
         */
        public static boolean getLock(final String key, final String value,
                                      final long timeout, final TimeUnit unit,
                                      boolean recordLog) {
            if (recordLog) {
                log.info("getLock(...) => key -> {},value -> {},timeout -> {},unit -> {},recordLog -> {}",
                        key, value, timeout, unit, recordLog);
            }
            Boolean result = redisTemplate.execute((RedisConnection connection) ->
                    connection.set(key.getBytes(StandardCharsets.UTF_8),
                            value.getBytes(StandardCharsets.UTF_8),
                            Expiration.seconds(unit.toSeconds(timeout)),
                            RedisStringCommands.SetOption.SET_IF_ABSENT)
            );
            if (recordLog) {
                log.info("getLock(...) => result -> {}", result);
            }
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Release (distributed) lock
         * <p>
         * Note: this method can guarantee (through the uniqueness of value): the lock added by yourself can only be released by yourself
         * Note: when the lock expires, it will also be automatically deleted and released by redis
         *
         * @param key   Lock name
         * @param value value corresponding to lock name
         * @return Whether the lock is released successfully
         */
        public static boolean releaseLock(final String key, final String value) {
            log.info("releaseLock(...) => key -> {},lockValue -> {}", key, value);
            Boolean result = redisTemplate.execute((RedisConnection connection) ->
                    connection.eval(RELEASE_LOCK_LUA.getBytes(),
                            ReturnType.BOOLEAN, 1,
                            key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8))
            );
            log.info("releaseLock(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * Release the lock and do not verify the value value corresponding to the key
         * <p>
         * Note: releasing the lock in this way may cause the lock added by yourself to be released by others
         * Therefore, it is not recommended to release the lock in this way
         *
         * @param key Lock name
         */
        @Deprecated
        public static void releaseLock(final String key) {
            KeyOps.delete(key);
        }
    }

    /**
     * When using Pipeline or Transaction to operate redis, the result (here) will return null (no matter whether the actual operation in redis is successful or not)
     * At this point, if you try to convert null to basic type data, this exception will be thrown
     * <p>
     * That is, some methods in this tool class do not want to use Pipeline or Transaction to operate redis
     * <p>
     * Note: Pipeline or Transaction is not enabled by default. See the source code for details:
     *
     * @see LettuceConnection#isPipelined()
     * @see LettuceConnection#isQueueing()
     * @see JedisConnection#isPipelined()
     * @see JedisConnection#isQueueing()
     */
    public static class RedisOpsResultIsNullException extends NullPointerException {

        public RedisOpsResultIsNullException() {
            super();
        }

        public RedisOpsResultIsNullException(String message) {
            super(message);
        }
    }

    /**
     * Provide some basic function support
     */
    public static class Helper {

        /**
         * Default splice
         */
        public static final String DEFAULT_SYMBOL = ":";

        /**
         * Splice args
         *
         * @see Helper#joinBySymbol(String, String...)
         */
        public static String join(String... args) {
            return Helper.joinBySymbol(DEFAULT_SYMBOL, args);
        }

        /**
         * Splice args using symbol
         *
         * @param symbol Separator, such as: [:]
         * @param args   Array of elements to be spliced, such as: [a b c]
         * @return Spliced string, such as [a:b:c]
         */
        public static String joinBySymbol(String symbol, String... args) {
            if (symbol == null || symbol.trim().length() == 0) {
                throw new RuntimeException(" symbol must not be empty!");
            }
            if (args == null || args.length == 0) {
                throw new RuntimeException(" args must not be empty!");
            }
            StringBuilder sb = new StringBuilder(16);
            for (String arg : args) {
                sb.append(arg).append(symbol);
            }
            sb.replace(sb.length() - symbol.length(), sb.length(), "");
            return sb.toString();
        }

    }
}

4. Create a new entity class and control class

pojo:

package com.zj.code.pojo;


import lombok.Data;

@Data
public class User {
    private String account;
}
UserController: 
package com.zj.code.controller;


import com.yzm.yzmspringbootstarter.EmailSender;
import com.zj.code.pojo.User;
import com.zj.code.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;


@RestController
@RequestMapping("/user")
public class UserController {


    private EmailSender sender;


    @GetMapping("/register")
    public String register(User user) {
        if (user.getAccount().length() < 5) {
            return "Non compliance with rules";
        }
        String yzm = RedisUtil.StringOps.get(user.getAccount());
        if (yzm == null) {
            yzm = sender.sendText(user.getAccount());
            RedisUtil.StringOps.setEx(user.getAccount(), yzm, 1, TimeUnit.MINUTES);
            return "Verification code sent";
        }
        return "Verification code or validity period";
    }

    /**
     * Judge whether the current verification code is consistent with the verification code in the cache
     * @param user  user
     * @param yzm   Current verification code
     * @return     Is the current verification code consistent with the user's verification in the cache
     */
    @GetMapping("/verify")
    public String verify(User user,String yzm) {
        String code = RedisUtil.StringOps.get(user.getAccount());
         if(code==null){
             return "Invalid verification code";
         }
        return code.equals(yzm)?"yes":"no";
    }

}

An error occurred after running:

The reason is that there is no automatic injection annotation

Run successfully

2. Optimize mailbox usage

When we register a mailbox, the registration is often slow. The ideal state should be that the registration is successful first, and then the mailbox is sent successfully

Solution:

1. Message queue

2. Asynchronous operation

The second approach is usually used, using asynchronous operations

Steps:

1. Create a task class to send verification code

package com.zj.code.util;


import com.yzm.yzmspringbootstarter.EmailSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class Task {

    @Autowired
    private EmailSender sender;

    @Async
  public void sendYzm(String account){
     String yzm = sender.sendText(account);
      RedisUtil.StringOps.setEx(account, yzm, 1, TimeUnit.MINUTES);

  }


}

Note:

Be sure to inject comments, otherwise an error will be reported

among

@Component (declare this class as a bean object and leave it to spring for management)

@Autowired (inject other classes)

@Async (asynchronous annotation) these three annotations are particularly important

2. Inject the start asynchronous annotation into the start class

@EnableAsync

3. Inject task annotation into the controller class,

@Autowired
private Task task;

package com.zj.code.controller;


import com.yzm.yzmspringbootstarter.EmailSender;
import com.zj.code.pojo.User;
import com.zj.code.util.RedisUtil;
import com.zj.code.util.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;


@RestController
@RequestMapping("/user")
public class UserController {



    @Autowired
    private Task task;


    @GetMapping("/register")
    public String register(User user) {
        if (user.getAccount().length() < 5) {
            return "Non compliance with rules";
        }
        String yzm = RedisUtil.StringOps.get(user.getAccount());
        if (yzm == null) {
         task.sendYzm(user.getAccount());
            return "Verification code sent";
        }
        return "Verification code or validity period";
    }

    /**
     * Judge whether the current verification code is consistent with the verification code in the cache
     * @param user  user
     * @param yzm   Current verification code
     * @return     Is the current verification code consistent with the user's verification in the cache
     */
    @GetMapping("/verify")
    public String verify(User user,String yzm) {
        String code = RedisUtil.StringOps.get(user.getAccount());
         if(code==null){
             return "Invalid verification code";
         }
        return code.equals(yzm)?"yes":"no";
    }

}

4. Test

Obviously, compared with the previous running results, using asynchronous operation can improve the running speed of the program. For this, the real verification code has not been sent.

That's all for today's knowledge. I hope it can help you. It's not easy to make. Praising is my greatest encouragement.

Keywords: Spring Boot

Added by Gillesie on Fri, 11 Feb 2022 22:29:57 +0200