【RedisTemplate】SpringDataRedis(Spring中对Redis模块的整合)

SpringDataRedis简介:

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。

官网地址:https://spring.io/projects/spring-data-redis

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)
  • 提供了RedisTemplate统一API来操作Redis
  • 支持Redis的发布订阅模型
  • 支持Redis哨兵和Redis集群
  • 支持基于Lettuce的响应式编程
  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
  • 支持基于Redis的JDKCollection实现

RedisTemplate对Redis操作类型:

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
在这里插入图片描述

SpringDataRedis的使用步骤:

1、引入spring-boot-starter-data-redis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--jedis或者redis底层都会基于commons库来实现连接池效果-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

2、在配置文件中配置Redis信息

server.port=8087
spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=123456
#连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.pool.max-idle=8
#连接池中的最小空闲连接
spring.redis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=30000

 =====另一种配置方式======
 spring:
  redis:
    host: 127.0.0.1 # ip
    port: 6379 # 端口
    password: 123456 # 密码
    lettuce: # 默认的客户端是lettuce,若要使用jedis需要添加依赖
      pool:
        max-active: 8  # 最大连接
        max-idle: 8 # 最大空闲连接
        min-idle: 0 # 最小空闲连接
        max-wait: 100ms # 连接等待时间
    database: 0 # 选择库

3、注入RedisTemplate后,即可正常调用方法

@SpringBootTest
class RedisTemplateApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testString() {
        redisTemplate.opsForValue().set("name","李四");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }

}

=========输出结果========
name = 李四

RedisTemplate的序列化方式:

现象: 在控制台能够看见正常的key和value,但是我们通过redis客户端查看时发现结果是这样的。
在这里插入图片描述原因: RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化;
通过查看RedisTemplate源码可以发现常见的类型都实现了序列化:
在这里插入图片描述查看源码RedisTemplate可以发现默认是采用JDK序列化;这种方式会在写入前把Object序列化为字节的形式,然后进行存储,缺点很明显:可读性差、占用内存。所以我们需要修改默认的JDK序列化方式:
在这里插入图片描述我们希望在redis中存的,所见及所得,而不是进行jdk序列化之后的样子,所以我们需要改变序列化方式。

在这里插入图片描述如上图所示,是主要的集中序列化方式。其中,key为字符串的时候我们通常使用StringRedisSerializer方式,value为字符串的时候,通常使用GenericJackson2JsonRedisSerializer方式。

1、自定义RedisTemplate序列化方法:

1、配置Json序列化的依赖Jackson:

<!--  jackson依赖  -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

2、RedisConfig配置类:

@Configuration
public class RedisConfig {

    /**
      * 描述信息: key和hashKey采用string序列化方式 alue和hashValue采用Json序列化方式
      *
      * @date  2023/05/17
      * @param redisConnectionFactory
      * @return org.springframework.data.redis.core.RedisTemplate<java.lang.String,java.lang.Object>
      **/
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //创建Template
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //key和hashKey采用string序列化方式
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        //value和hashValue采用Json序列化方式
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        return redisTemplate;
    }
}

3、测试:
在这里插入图片描述

2、对象序列化:

    @Test
    void testSaveUser() {
        redisTemplate.opsForValue().set("user:100",new User("小红",29));
        User user = (User) redisTemplate.opsForValue().get("user:100");
        System.out.println("user = " + user);
    }

在这里插入图片描述尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,如图:

在这里插入图片描述当我们传入的Value为实体类对象的时候,会用 GenericJackson2JsonRedisSerializer序列化器把java对象转为JSON格式,然后再存入Redis库中,在我们使用redisTemplate.opsForValue().get方法获取数据时,通过存入的@class属性,把JSON反序列化成JAVA对象。

这样一来我们在IDEA的控制器上很直观的就可以看到数据,但是也存在了一个缺点->浪费内存,因为我们要存放@class这一段额外的数据来反序列化JSON字符串。

为了节省内存空间,我们一般实际中并不会使用JSON序列化器(GenericJackson2JsonRedisSerializer序列化器)来处理value,而是统一使用String序列化器(RedisSerializer.string序列化器),要求只能存储String类型的key和value。我们只需要在存入数据时,手动的把JAVA对象转变为JSON格式字符串,然后取数据时,再把JSON转回JAVA对象就好了。

在这里插入图片描述Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:

@SpringBootTest
class RedisStringRTDemoApplicationTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    void testObject() throws JsonProcessingException {
        // 1.创建对象
        Student s1 = new Student("李四", '男', 22);
        // 2.手动序列化
        String json = mapper.writeValueAsString(s1);
        // 3.存储一条数据
        stringRedisTemplate.opsForValue().set("student:2", json);
        // 4.获取数据
        String jsonStudent = stringRedisTemplate.opsForValue().get("student:2");
        // 5.手动反序列化
        Student s = mapper.readValue(jsonStudent, Student.class);
        System.out.println("s:" + s);
    }
}

在这里插入图片描述在这里插入图片描述参考链接:https://blog.csdn.net/weixin_43811057/article/details/130735304

Redis的String数据结构 相关的使用方法:

1、set(K var1, V var2)

新增一个字符串类型的值var1是keyvar2是值key存在就覆盖,不存在新增

redisTemplate.opsForValue().set("BBB","你好");

2、set(K key, V value, Duration timeout)

新增一个字符串类型的值,同时设置过期时间var1是keyvar2是值key存在就覆盖,不存在新增

redisTemplate.opsForValue().set("BBB","你好", Duration.ofMinutes(1));

3、set(K var1, V var2, long var3, TimeUnit var5)

新增一个字符串类型的值,同时设置过期时间var1是key,var2是值,key存在就覆盖,不存在新增

redisTemplate.opsForValue().set("BBB","你好", 1, TimeUnit.MINUTES);

TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒

4、append(K var1, String var2)

给对应的key追加value,key不存在直接新增

redisTemplate.opsForValue().append("AAA", "哈哈哈");

5、set(K key1, V v1, long v2)

将key的值从下标1往后替换为新的value,key不存在相当于新增

redisTemplate.opsForValue().set("BBB","您的",1);

6、setBit(K var1, long var2, boolean var4)

key键对应的值value对应的ascii码,在offset的位置(从左向右数)变为value

redisTemplate.opsForValue().setBit("BBB", 0, true);

7、getBit(K var1, long var2)

判断指定的位置ASCII码的bit位是否为1

redisTemplate.opsForValue().getBit("BBB", 1);

8、setIfAbsent(K var1, V var2);

如果key不存在则新增,存在则不改变已经有的值

redisTemplate.opsForValue().setIfAbsent("BBB", "好的");

9、setIfAbsent(K var1, V var2, long var3, TimeUnit var5)

如果key不存在则新增,存在则不改变已经有的值,同时设置过期时间

redisTemplate.opsForValue().setIfAbsent("AAA", "好的", 1, TimeUnit.MINUTES);

10、setIfAbsent(K key, V value, Duration timeout)

如果key不存在则新增,存在则不改变已经有的值,同时设置过期时间

redisTemplate.opsForValue().setIfAbsent("BBB", "好的", Duration.ofMinutes(1));

11、setIfPresent(K var1, V var2)

如果key存在则修改,不存在则不改变已经有的值

redisTemplate.opsForValue().setIfPresent("BBB", "好的");

12、setIfPresent(K var1, V var2, long var3, TimeUnit var5)

如果key存在则修改,不存在则不改变已经有的值,同时设置过期时间

redisTemplate.opsForValue().setIfPresent("BBB", "好的",1, TimeUnit.MINUTES);

13、setIfPresent(K key, V value, Duration timeout)

如果key存在则修改,不存在则不改变已经有的值,同时设置过期时间

redisTemplate.opsForValue().setIfPresent("BBB", "好的",Duration.ofMinutes(1));

14、getAndSet(K var1, V var2)

获取key对应的值,如果key存在则修改,不存在则新增

redisTemplate.opsForValue().getAndSet("BBB", "心情");

15、increment(K var1)

以增量的方式(默认增量为1)将long值存储在变量中(value为其他类型时报错),返回最新值

redisTemplate.opsForValue().increment("AAA");

16、increment(K var1, long var2)

以指定增量的方式将Long值存储在变量中,返回最新值

redisTemplate.opsForValue().increment("AAA",2);

17、increment(K var1, double var2)

以指定增量的方式将Double值存储在变量中,返回最新值

redisTemplate.opsForValue().increment("AAA", 3.2);

18、decrement(K var1)

以递减的方式(默认为1)将long值存储在变量中(value为其他类型时报错,Double也不行,只能为Long),返回最新值

redisTemplate.opsForValue().decrement("AAA");

19、decrement(K var1, long var2)

以指定递减量递减的方式将long值存储在变量中(value为其他类型时报错,Double也不行,只能为Long),返回最新值

redisTemplate.opsForValue().decrement("AAA",2);

20、size(K var1)

获取指定key对应值的长度

redisTemplate.opsForValue().size("BBB");

21、get(Object var1)

获取指定的key对应的值

String BBB = (String) redisTemplate.opsForValue().get("BBB");
System.out.println("BBB = " + BBB);

22、get(K var1, long var2, long var4)

获取key指定下标之间对应的值

String BBB = redisTemplate.opsForValue().get("BBB",0,1);
System.out.println("BBB = " + BBB);

23、multiSet(Map<? extends K, ? extends V> var1)

将map中的key分别作为不同的key存到Redis中(见截图)
在这里插入图片描述

若某个key已经存在则替换为新值,其他不存在的则新增
map中5个key,3个存在Redis中,2个没有,结果就是3个值被修改,2个新增

Map valueMap = new HashMap();
valueMap.put("valueMap1","aa");
valueMap.put("valueMap2","bb");
valueMap.put("valueMap3","cc");
valueMap.put("valueMap4","ee");
redisTemplate.opsForValue().multiSet(valueMap);

24、multiSetIfAbsent(Map<? extends K, ? extends V> var1)

将map中的key分别作为不同的key存到Redis中

若某个key已经存在不做修改,不存在的则新增(map中的key在Redis中都不存在时才新增)

map中5个key,3个存在Redis中,2个没有,结果就是不会新增不会修改,若map中5个key,5个都不存在Redis中,则新增

Map valueMap = new HashMap();
valueMap.put("valueMap1","aa");
valueMap.put("valueMap2","bb");
valueMap.put("valueMap3","cc");
valueMap.put("valueMap4","ee");
valueMap.put("valueMap5","ff");
redisTemplate.opsForValue().multiSetIfAbsent(valueMap);

25、multiGet(Collection var1)

根据集合中的key取出对应的value值

List paraList = new ArrayList();
paraList.add("valueMap1");
paraList.add("valueMap2");
paraList.add("valueMap3");
List list = redisTemplate.opsForValue().multiGet(paraList);

26、存入JSON字符串(对象集合)

对象和对象集合一定要转成JSON存放,容易解析

List<MPEntity> list = mpService.list();
redisTemplate.opsForValue().set("BBB", JSON.toJSONString(list));

在这里插入图片描述

获取解析JSON字符串

String bbb = (String) redisTemplate.opsForValue().get("BBB");
List<MPEntity> mpEntities = JSON.parseArray(bbb, MPEntity.class);
System.out.println("mpEntities = " + mpEntities);

在这里插入图片描述

参考链接:https://blog.csdn.net/qq_37131747/article/details/125673505

27、type:返回传入key所存储的值的类型

redisTemplate.opsForValue().type(key);

28、rename:修改redis中key的名称

 public void renameKey(String oldKey, String newKey) {
    redisTemplate.opsForValue().rename(oldKey, newKey);
}

29、renameIfAbsent:如果旧值key存在时,将旧值改为新值

public Boolean renameOldKeyIfAbsent(String oldKey, String newKey) {
    return redisTemplate.opsForValue().renameIfAbsent(oldKey, newKey);
}

30、hasKey:判断是否有key所对应的值,有则返回true,没有则返回false

redisTemplate.hasKey(key)

31、delete:删除单个key值

redisTemplate.delete(key)

批量删除key:
redisTemplate.delete(keys) //其中keys:Collection<K> keys

32、expire:设置过期时间

public Boolean expire(String key, long timeout, TimeUnit unit) {
    return redisTemplate.expire(key, timeout, unit);
 }
 public Boolean expireAt(String key, Date date) {
    return redisTemplate.expireAt(key, date);
  }

33、getExpire:返回当前key所对应的剩余过期时间

redisTemplate.getExpire(key);


//返回剩余过期时间并且指定时间单位  
public Long getExpire(String key, TimeUnit unit) {
    return redisTemplate.getExpire(key, unit);
}

34、getPatternKey:查找匹配的key值,返回一个Set集合类型

public Set<String> getPatternKey(String pattern) {
    return redisTemplate.keys(pattern);
}

35、persistKey:将key持久化保存

public Boolean persistKey(String key) {
    return redisTemplate.persist(key);
}

36、moveToDbIndex:将当前数据库的key移动到指定redis中数据库当中

public Boolean moveToDbIndex(String key, int dbIndex) {
    return redisTemplate.move(key, dbIndex);
}

Redis的Hash类型 相关的使用方法:

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。

1、get:获取变量中的指定map键是否有值,如果存在该map键则获取值,没有则返回null

redisTemplate.opsForHash().get(key, field)

2、entries:获取变量中的键值对

public Map<Object, Object> hGetAll(String key) {
    return redisTemplate.opsForHash().entries(key);
}

3、put:新增hashMap值

redisTemplate.opsForHash().put(key, hashKey, value)

4、以map集合的形式添加键值对

public void hPutAll(String key, Map<String, String> maps) {
    redisTemplate.opsForHash().putAll(key, maps);
}

5、putIfAbsent:仅当hashKey不存在时才设置

public Boolean hashPutIfAbsent(String key, String hashKey, String value) {
    return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}

6、delete:删除一个或者多个hash表字段

public Long hashDelete(String key, Object... fields) {
    return redisTemplate.opsForHash().delete(key, fields);
}

7、hasKey:查看hash表中指定字段是否存在

public boolean hashExists(String key, String field) {
    return redisTemplate.opsForHash().hasKey(key, field);
}

8、increment:给哈希表key中的指定字段的整数值加上增量

public Long hashIncrBy(String key, Object field, long increment) {
    return redisTemplate.opsForHash().increment(key, field, increment);
}
 public Double hIncrByDouble(String key, Object field, double delta) {
    return redisTemplate.opsForHash().increment(key, field, delta);
}

9、keys:获取所有hash表中字段

redisTemplate.opsForHash().keys(key)

10、opsForHash().values:获取hash表中存在的所有的值

public List<Object> hValues(String key) {
    return redisTemplate.opsForHash().values(key);
}

11、size:获取hash表中字段的数量

redisTemplate.opsForHash().size(key)

12、opsForHash().scan:匹配获取键值对,ScanOptions.NONE为获取全部键对

public Cursor<Entry<Object, Object>> hashScan(String key, ScanOptions options) {
    return redisTemplate.opsForHash().scan(key, options);
}

Redis的List类型 相关的使用方法:

1、index:通过索引获取列表中的元素

redisTemplate.opsForList().index(key, index)

2、range:获取列表指定范围内的元素(start开始位置, 0是开始位置,end 结束位置, -1返回所有)

redisTemplate.opsForList().range(key, start, end)

3、leftPush:存储在list的头部,即添加一个就把它放在最前面的索引处

redisTemplate.opsForList().leftPush(key, value)

4、leftPushAll:把多个值存入List中(value可以是多个值,也可以是一个Collection value)

redisTemplate.opsForList().leftPushAll(key, value)

5、leftPushIfPresent:List存在的时候再加入

redisTemplate.opsForList().leftPushIfPresent(key, value)

6、rightPush:按照先进先出的顺序来添加(value可以是多个值,或者是Collection var2)

redisTemplate.opsForList().rightPush(key, value)
redisTemplate.opsForList().rightPushAll(key, value)

7、set:设置指定索引处元素的值

redisTemplate.opsForList().set(key, index, value)

8、leftPop:移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)

redisTemplate.opsForList().leftPop(key)
redisTemplate.opsForList().leftPop(key, timeout, unit)

9、rightPop:移除并获取列表最后一个元素

redisTemplate.opsForList().rightPop(key)
redisTemplate.opsForList().rightPop(key, timeout, unit)

10、rightPopAndLeftPush:从一个队列的右边弹出一个元素并将这个元素放入另一个指定队列的最左边

redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey)
redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit)

11、删除集合中值等于value的元素

(index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素; index<0, 从尾部开始删除第一个值等于value的元素)

redisTemplate.opsForList().remove(key, index, value)

12、trim:将List列表进行剪裁

redisTemplate.opsForList().trim(key, start, end)

13、获取当前key的List列表长度

redisTemplate.opsForList().size(key)

Redis的Set类型 相关的使用方法:

1、add:添加元素

redisTemplate.opsForSet().add(key, values)

2、remove:移除元素(单个值、多个值)

redisTemplate.opsForSet().remove(key, values)

3、size:获取集合的大小

redisTemplate.opsForSet().size(key)

4、isMember:判断集合是否包含value

redisTemplate.opsForSet().isMember(key, value)

5、intersect:获取两个集合的交集

(key对应的无序集合与otherKey对应的无序集合求交集)

redisTemplate.opsForSet().intersect(key, otherKey)

//获取多个集合的交集(Collection var2)
redisTemplate.opsForSet().intersect(key, otherKeys)      

6、intersectAndStore:集合与otherKey集合的交集存储到destKey集合中(其中otherKey可以为单个值或者集合)

redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey)

//key集合与多个集合的交集存储到destKey无序集合中
redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey)

7、获取两个或者多个集合的并集(otherKeys可以为单个值或者是集合)

redisTemplate.opsForSet().union(key, otherKeys)

8、unionAndStore:key集合与otherKey集合的并集存储到destKey中(otherKeys可以为单个值或者是集合)

redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey)

9、difference:获取两个或者多个集合的差集(otherKeys可以为单个值或者是集合)

redisTemplate.opsForSet().difference(key, otherKeys)

10、differenceAndStore:差集存储到destKey中(otherKeys可以为单个值或者集合)

redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey)

11、members:获取集合中的所有元素

redisTemplate.opsForSet().members(key)

12、randomMembers:随机获取集合中count个元素

redisTemplate.opsForSet().randomMembers(key, count)

//随机获取集合中的一个元素
redisTemplate.opsForSet().randomMember(key)

13、scan:遍历set类似于Interator(ScanOptions.NONE为显示所有的)

redisTemplate.opsForSet().scan(key, options)

Redis的zSet类型 相关的使用方法:

ZSetOperations提供了一系列方法对有序集合进行操作

1、add:添加元素(有序集合是按照元素的score值由小到大进行排列)

redisTemplate.opsForZSet().add(key, value, score)

2、remove:删除对应的value,value可以为多个值

redisTemplate.opsForZSet().remove(key, values)

3、incrementScore:增加元素的score值,并返回增加后的值

redisTemplate.opsForZSet().incrementScore(key, value, delta)

4、rank:返回元素在集合的排名,有序集合是按照元素的score值由小到大排列

redisTemplate.opsForZSet().rank(key, value)

5、reverseRank:返回元素在集合的排名,按元素的score值由大到小排列

redisTemplate.opsForZSet().reverseRank(key, value)

6、reverseRangeWithScores:获取集合中给定区间的元素(start 开始位置,end 结束位置, -1查询所有)

redisTemplate.opsForZSet().reverseRangeWithScores(key, start,end)

7、reverseRangeByScore:按照Score值查询集合中的元素,结果从小到大排序

redisTemplate.opsForZSet().reverseRangeByScore(key, min, max)

//从高到低的排序集中获取分数在最小和最大值之间的元素
redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end)

8、count:根据score值获取集合元素数量

redisTemplate.opsForZSet().count(key, min, max)

9、size:获取集合的大小

redisTemplate.opsForZSet().size(key)

10、score:获取集合中key、value元素对应的score值

redisTemplate.opsForZSet().score(key, value)

11、removeRange:移除指定索引位置处的成员

redisTemplate.opsForZSet().removeRange(key, start, end)

12、removeRangeByScore:移除指定score范围的集合成员

redisTemplate.opsForZSet().removeRangeByScore(key, min, max)

13、unionAndStore:获取key和otherKey的并集并存储在destKey中(其中otherKeys可以为单个字符串或者字符串集合

redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey)

14、intersectAndStore:获取key和otherKey的交集并存储在destKey中(其中otherKeys可以为单个字符串或者字符串集合)

redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey)

15、遍历集合(和iterator一模一样)

Cursor<TypedTuple<Object>> scan = opsForZSet.scan("test3", ScanOptions.NONE);
while (scan.hasNext()){
ZSetOperations.TypedTuple<Object> item = scan.next();
System.out.println(item.getValue() + ":" + item.getScore());
}

参考链接:https://blog.csdn.net/lanfeng_lan/article/details/121152461

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值