使用Spring Data Redis操作Redis(一) 很全面

Spring-Data-Redis项目(简称SDR)对Redis的Key-Value数据存储操作提供了更高层次的抽象,类似于Spring Framework对JDBC支持一样。

项目主页: http://projects.spring.io/spring-data-redis/

项目文档: http://docs.spring.io/spring-data/redis/docs/1.5.0.RELEASE/reference/html/

本文主要介绍Spring Data Redis的实际使用。

1.Spring Data Redis 1.5新特性

增加了Redis HyperLogLog命令PFADD,PFCOUNT,PFMERGE

可以使用Jackson基于RedisSerializer对Java类型序列化

使用PropertySource配置Redis Sentinel连接,目前仅Jedis客户端支持

2.Spring Data Redis ?

Spring Data Redis使得在Spring应用中读写Redis数据库更加容易。

Spring Data Redis提供了四种Redis服务的Java客户端包的集成,分别是 Jedis ,JRedis , SRP and Lettuce

3.版本要求

Spring Data Redis1.2.x要求JDK1.6+,Spring Framwork3.2.8+

Key-Value存储服务Redis 2.6.x+

4.搭建环境

本文假设已经安装完成了Redis服务,并成功运行。

创建maven项目,添加依赖的Jar,本文主要使用jedis

<dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>1.5.0.RELEASE</version>
    </dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>1.5.0.RELEASE</version>
    </dependency>
<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.6.2</version>
    </dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.6.2</version>
    </dependency>

5.连接Redis服务

在Spring Data Redis中通过org.springframework.data.redis.connection包中的RedisConnection和RedisConnectionFactory类来获取Redis连接。

5.1配置JedisConnectionFactory

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="server" p:port="6379" />

</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="server" p:port="6379" />

</beans>

5.2配置Jredis,SRP,Lettuce的配置和上面配置类似,只需要配置对应的RedisConnectionFactory接口实现接口,它门位于如下包中:

5.3关于RedisConnectionFactory注意问题

上面四种连接器并不是都支持Redis的所有特性,它们之间有差异性,如果调用方法在Connection API中不支持则抛出“UnsupportedOperationException”异常,具体情况需要了解对应的Redis Java客户端Jar的实现。

5.4关于Redis Sentinel(这里暂称为:哨兵)支持

Redis Sentinel监听主服务,再主服务发生故障时能够切换至从服务,将从服务升为主服务来保证故障恢复,使用该功能需要在JedisConnectionFactory设置RedisSentinelConfiguration属性,目前Jedis对Redis Sentinel提供支持。

编码方式如下:

public RedisConnectionFactory jedisConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
  .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
  return new JedisConnectionFactory(sentinelConfig);
} RedisConnectionFactory jedisConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
  .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
  return new JedisConnectionFactory(sentinelConfig);
}

在Spring容器中配置:

<bean id="sentinelConfig"
    class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
    <constructor-arg name="master" value="mymaster" />
    <constructor-arg name="sentinelHostAndPorts">
      <set>
        <value>192.168.88.153:26379</value>
        <value>192.168.88.153:26380</value>
        <value>192.168.88.153:26382</value>
      </set>
    </constructor-arg>
  </bean>
  
  <bean id="jedisConnectionFactory"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <constructor-arg ref="sentinelConfig" />
  </bean>
    <constructor-arg name="master" value="mymaster" />
    <constructor-arg name="sentinelHostAndPorts">
      <set>
        <value>192.168.88.153:26379</value>
        <value>192.168.88.153:26380</value>
        <value>192.168.88.153:26382</value>
      </set>
    </constructor-arg>
  </bean>
  
  <bean id="jedisConnectionFactory"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <constructor-arg ref="sentinelConfig" />
  </bean>

注意:在配置Redis的sentinel.conf文件时注意使用外部可以访问的ip地址,因为当redis-sentinel服务和redis-server在同一台机器的时候,主服务发生变化时配置文件中将主服务ip变为127.0.0.1,这样外部就无法访问了。如果应用程序,Redis服务在同一台机器则不存在这样的隐患,具体情况则更加实际的网络环境。

配置好之后,在实例化JedisConnectionFactory之后,可见如下日志:

2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Trying to find master from available Sentinels...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Redis master running at 192.168.88.153:6384, starting Sentinel listeners...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initPool
信息: Created JedisPool to master at 192.168.88.153:6384:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Trying to find master from available Sentinels...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Redis master running at 192.168.88.153:6384, starting Sentinel listeners...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initPool
信息: Created JedisPool to master at 192.168.88.153:6384

实验环境中192.168.88.153:6384的Redis实例是主服务。

5.5下面通过一组代码展示具体使用

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestJedis {

  public static ApplicationContext ctx;

  public static JedisConnectionFactory jedisConnetionFactory;

  public JedisConnection jedisConnection;

  @SuppressWarnings("unchecked")
  @BeforeClass
  public static void setBeforeClass() {
    ctx = new ClassPathXmlApplicationContext("spring-redis.xml");
    jedisConnetionFactory = (JedisConnectionFactory) ctx
        .getBean("jedisConnectionFactory");
  }

  @Before
  public void setBefore() {
    jedisConnection = jedisConnetionFactory.getConnection();
  }

  @After
  public void setAfter() {
    jedisConnection.close();
  }

  private void print(Collection<RedisServer> c) {
    for (Iterator<RedisServer> iter = c.iterator(); iter.hasNext();) {
      RedisServer rs = (RedisServer) iter.next();
      System.out.println(rs.getHost() + ":" + rs.getPort());
    }
  }

  // 简单测试JedisConnection
  @Ignore
  @Test
  public void test1() {
    if (!jedisConnection.exists(new String("zz").getBytes())) {
      jedisConnection.set(new String("zz").getBytes(),
          new String("zz").getBytes());
    }
  }

  @Ignore
  @Test
  public void test2() {
    Set<byte[]> keys = jedisConnection.keys(new String("*").getBytes());
    for (Iterator<byte[]> iter = keys.iterator(); iter.hasNext();) {
      System.out.println(new String(iter.next()));
    }
  }

  // 测试Sentinel
  @Ignore
  @Test
  public void test3() throws InterruptedException {
    if (jedisConnetionFactory.getSentinelConnection().isOpen()) {
      Collection<RedisServer> c = jedisConnetionFactory
          .getSentinelConnection().masters();
      print(c);
      RedisNode rn = new RedisNode("192.168.88.153", 6380);
      rn.setName("mymaster");
      c = jedisConnetionFactory.getSentinelConnection().slaves(rn);
      print(c);
    }

    for (int i = 0; i < 1000; i++) {
      jedisConnection.set(new String("k" + i).getBytes(), new String("v"
          + i).getBytes());
      Thread.sleep(1000);
    }
    Set<byte[]> keys = jedisConnection.keys(new String("k*").getBytes());
    Assert.assertEquals(1000, keys.size());
  }
}(MethodSorters.NAME_ASCENDING)
public class TestJedis {

  public static ApplicationContext ctx;

  public static JedisConnectionFactory jedisConnetionFactory;

  public JedisConnection jedisConnection;

  @SuppressWarnings("unchecked")
  @BeforeClass
  public static void setBeforeClass() {
    ctx = new ClassPathXmlApplicationContext("spring-redis.xml");
    jedisConnetionFactory = (JedisConnectionFactory) ctx
        .getBean("jedisConnectionFactory");
  }

  @Before
  public void setBefore() {
    jedisConnection = jedisConnetionFactory.getConnection();
  }

  @After
  public void setAfter() {
    jedisConnection.close();
  }

  private void print(Collection<RedisServer> c) {
    for (Iterator<RedisServer> iter = c.iterator(); iter.hasNext();) {
      RedisServer rs = (RedisServer) iter.next();
      System.out.println(rs.getHost() + ":" + rs.getPort());
    }
  }

  // 简单测试JedisConnection
  @Ignore
  @Test
  public void test1() {
    if (!jedisConnection.exists(new String("zz").getBytes())) {
      jedisConnection.set(new String("zz").getBytes(),
          new String("zz").getBytes());
    }
  }

  @Ignore
  @Test
  public void test2() {
    Set<byte[]> keys = jedisConnection.keys(new String("*").getBytes());
    for (Iterator<byte[]> iter = keys.iterator(); iter.hasNext();) {
      System.out.println(new String(iter.next()));
    }
  }

  // 测试Sentinel
  @Ignore
  @Test
  public void test3() throws InterruptedException {
    if (jedisConnetionFactory.getSentinelConnection().isOpen()) {
      Collection<RedisServer> c = jedisConnetionFactory
          .getSentinelConnection().masters();
      print(c);
      RedisNode rn = new RedisNode("192.168.88.153", 6380);
      rn.setName("mymaster");
      c = jedisConnetionFactory.getSentinelConnection().slaves(rn);
      print(c);
    }

    for (int i = 0; i < 1000; i++) {
      jedisConnection.set(new String("k" + i).getBytes(), new String("v"
          + i).getBytes());
      Thread.sleep(1000);
    }
    Set<byte[]> keys = jedisConnection.keys(new String("k*").getBytes());
    Assert.assertEquals(1000, keys.size());
  }
}

6.RedisTemplate支持

熟悉Spring的JdbcTemplate对象的话,应该大概能猜出来RedisTemplate的作用了,RedisTemplate对象对RedisConnection进行了封装,它提供了连接管理,序列化等功能,它对Redis的交互进行了更高层次的抽象。另外还提供了Redis操作命令的操作视图,这极大的方便和简化了Redis的操作。

下表是具体的操作视图接口类介绍:

Key类型操作
ValueOperationsRedis String/Value 操作
ListOperationsRedis List 操作
SetOperationsRedis Set 操作
ZSetOperationsRedis Sort Set 操作
HashOperationsRedis Hash 操作
Value约束操作
BoundValueOperationsRedis String/Value key 约束
BoundListOperationsRedis List key 约束
BoundSetOperationsRedis Set key 约束
BoundZSetOperationsRedis Sort Set key 约束
BoundHashOperationsRedis Hash key 约束

在org.springframework.data.redis.core包中对表中的接口都提供了相应的默认实现。

6.1RedisSerializer

Spring Data Redis提供了对Key-Value的序列号,在使用RedisTemplate对象是默认使用JdkSerializationRedisSerializer实现。还提供了其它的序列化实现如:Jackson2JsonRedisSerializer,JacksonJsonRedisSerializer,GenericToStringSerializer,StringRedisSerializer,OxmSerializer。

另外用户可以提供自己的序列化实现

6.2配置RedisTemplate

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" />
  <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory" />
  </bean>
  <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory" />
  </bean>

这里配置了RedisTemplate和StringRedisTemplate,不同之处在于StringRedisTemplate的Key-Value序列化使用的是StringRedisSerializer。使用StringRedisTemplate操作Redis之后的结果是读友好的。

另外对Hash类型而言,还有对应的HashKey序列化(其对应于Hash类型的字段名)。

6.3RedisTemplate的使用

// 测试RedisTemplate,自主处理key的可读性(String序列号)
  @Ignore
  @Test
  public void test4() {
    String key = "spring";
    ListOperations<String, String> lop = redisTemplate.opsForList();
    RedisSerializer<String> serializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(serializer);
    redisTemplate.setValueSerializer(serializer);
    // rt.setDefaultSerializer(serializer);

    lop.leftPush(key, "aaa");
    lop.leftPush(key, "bbb");
    long size = lop.size(key); // rt.boundListOps(key).size();
    Assert.assertEquals(2, size);
  }

  // 测试便捷对象StringRedisTemplate
  @Ignore
  @Test
  public void test5() {
    ValueOperations<String, String> vop = stringRedisTemplate.opsForValue();
    String key = "string_redis_template";
    String v = "use StringRedisTemplate set k v";
    vop.set(key, v);
    String value = vop.get(key);
    Assert.assertEquals(v, value);
  }
  @Ignore
  @Test
  public void test4() {
    String key = "spring";
    ListOperations<String, String> lop = redisTemplate.opsForList();
    RedisSerializer<String> serializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(serializer);
    redisTemplate.setValueSerializer(serializer);
    // rt.setDefaultSerializer(serializer);

    lop.leftPush(key, "aaa");
    lop.leftPush(key, "bbb");
    long size = lop.size(key); // rt.boundListOps(key).size();
    Assert.assertEquals(2, size);
  }

  // 测试便捷对象StringRedisTemplate
  @Ignore
  @Test
  public void test5() {
    ValueOperations<String, String> vop = stringRedisTemplate.opsForValue();
    String key = "string_redis_template";
    String v = "use StringRedisTemplate set k v";
    vop.set(key, v);
    String value = vop.get(key);
    Assert.assertEquals(v, value);
  }

具体使用那种序列化策略则更加存储的Key-Value内容做权衡即可。

通过RedisTemplate中的方法的参数RedisCallback回调接口来获取RedisConnection,进一步操作Redis,比如事务控制。需要注意的是如果使用StringRedisTemplate则返回的是StringRedisConnection对象。

测试RedisCallback代码示例:

// 测试Callback
  @Ignore
  @Test
  public void test61() {
    Long dbsize = (Long) stringRedisTemplate
        .execute(new RedisCallback<Object>() {
          @Override
          public Long doInRedis(RedisConnection connection)
              throws DataAccessException {
            StringRedisConnection stringRedisConnection=(StringRedisConnection)connection;
            return stringRedisConnection.dbSize();
          }
        });
    System.out.println("dbsize:" + dbsize);
  }
  @Ignore
  @Test
  public void test61() {
    Long dbsize = (Long) stringRedisTemplate
        .execute(new RedisCallback<Object>() {
          @Override
          public Long doInRedis(RedisConnection connection)
              throws DataAccessException {
            StringRedisConnection stringRedisConnection=(StringRedisConnection)connection;
            return stringRedisConnection.dbSize();
          }
        });
    System.out.println("dbsize:" + dbsize);
  }

测试SessionCallback代码示例:

@Test
  public void test62() {
    List<Object> txresult = stringRedisTemplate
        .execute(new SessionCallback<List<Object>>() {

          @Override
          public List<Object> execute(RedisOperations operations)
              throws DataAccessException {
            operations.multi();
            operations.opsForHash().put("hkey", "multikey4",
                "multivalue4");
            operations.opsForHash().get("hkey", "k1");

            return operations.exec();
          }

        });
    for (Object o : txresult) {
      System.out.println(o);
      /**
       * 0. false/true 
       * 1. v1
       */
    }
  }
  public void test62() {
    List<Object> txresult = stringRedisTemplate
        .execute(new SessionCallback<List<Object>>() {

          @Override
          public List<Object> execute(RedisOperations operations)
              throws DataAccessException {
            operations.multi();
            operations.opsForHash().put("hkey", "multikey4",
                "multivalue4");
            operations.opsForHash().get("hkey", "k1");

            return operations.exec();
          }

        });
    for (Object o : txresult) {
      System.out.println(o);
      /**
       * 0. false/true 
       * 1. v1
       */
    }
  }

说明:

  • 在事务中的操作返回都是null,因此不能在execute中对操作的结果进行处理。比如这里get("hkey","k1")的结果。

  • Hash操作中如果字段存在则返回false(redis中是0),不存在返回true(redis中是1)与字段对应的值是否更新无关联。

  • exec()方法返回List<Object>中的对象对应事务中执行的每条命令的返回结果。

7.执行Lua脚本

Redis中执行Lua脚本开发思路: http://aiilive.blog.51cto.com/1925756/1626372

Spring Data Redis中执行Lua脚本更加便利,下面示例展示使用RedisTemplate对象执行Lua脚本。

// 测试Lua脚本
  @Ignore
  @Test
  public void test71() {
    List<String> keys = new ArrayList<String>();
    RedisScript<Long> script = new DefaultRedisScript<Long>(
        "local size = redis.call('dbsize'); return size;", Long.class);
    Long dbsize = stringRedisTemplate
        .execute(script, keys, new Object[] {});
    System.out.println("sha1:" + script.getSha1());
    System.out.println("Lua:" + script.getScriptAsString());
    System.out.println("dbsize:" + dbsize);
  }

  @Test
  public void test72() {
    DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>();
    /**
     * isexistskey.lua内容如下:
     * 
     * return tonumber(redis.call("exists",KEYS[1])) == 1;
     */
    script.setScriptSource(new ResourceScriptSource(new ClassPathResource(
        "/isexistskey.lua")));

    script.setResultType(Boolean.class);// Must Set

    System.out.println("script:" + script.getScriptAsString());
    Boolean isExist = stringRedisTemplate.execute(script,
        Collections.singletonList("k2"), new Object[] {});
    Assert.assertTrue(isExist);
  }
  @Ignore
  @Test
  public void test71() {
    List<String> keys = new ArrayList<String>();
    RedisScript<Long> script = new DefaultRedisScript<Long>(
        "local size = redis.call('dbsize'); return size;", Long.class);
    Long dbsize = stringRedisTemplate
        .execute(script, keys, new Object[] {});
    System.out.println("sha1:" + script.getSha1());
    System.out.println("Lua:" + script.getScriptAsString());
    System.out.println("dbsize:" + dbsize);
  }

  @Test
  public void test72() {
    DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>();
    /**
     * isexistskey.lua内容如下:
     * 
     * return tonumber(redis.call("exists",KEYS[1])) == 1;
     */
    script.setScriptSource(new ResourceScriptSource(new ClassPathResource(
        "/isexistskey.lua")));

    script.setResultType(Boolean.class);// Must Set

    System.out.println("script:" + script.getScriptAsString());
    Boolean isExist = stringRedisTemplate.execute(script,
        Collections.singletonList("k2"), new Object[] {});
    Assert.assertTrue(isExist);
  }

8.支持类操作

在org.springframework.data.redis.support包中提供了各种可重用组件,这些组件可以应用到Redis存储,如atomic计数,JDK集合,Redis的类型集合(RedisList,RedisSet等)

<span style="color:#000080"><redis:collection <span style="color:#008080">id</span>=<span style="color:#dd1144">"springList"</span> <span style="color:#008080">key</span>=<span style="color:#dd1144">"springlist"</span>
    <span style="color:#008080">template</span>=<span style="color:#dd1144">"stringRedisTemplate"</span> <span style="color:#008080">type</span>=<span style="color:#dd1144">"LIST"</span> /></span>
@Ignore
  @Test
  public void test8() {
    RedisAtomicInteger rai = new RedisAtomicInteger("redis:atomic",
        jedisConnetionFactory);
    System.out.println(rai.get());
  }

  // 测试Redis Collection
  @Ignore
  @Test
  public void test9() {
    @SuppressWarnings("unchecked")
    RedisList<String> redisList = (RedisList<String>) ctx
        .getBean("springList");
    redisList.clear();
    redisList.addFirst("china");
    redisList.add("in");
    redisList.add("go");
    redisList.addLast("made");
    System.out.println(redisList.getKey());
  }
  @Test
  public void test8() {
    RedisAtomicInteger rai = new RedisAtomicInteger("redis:atomic",
        jedisConnetionFactory);
    System.out.println(rai.get());
  }

  // 测试Redis Collection
  @Ignore
  @Test
  public void test9() {
    @SuppressWarnings("unchecked")
    RedisList<String> redisList = (RedisList<String>) ctx
        .getBean("springList");
    redisList.clear();
    redisList.addFirst("china");
    redisList.add("in");
    redisList.add("go");
    redisList.addLast("made");
    System.out.println(redisList.getKey());
  }

 

 

部分赠送视频如下:

 

 

  • 1
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用Spring Data Redis的例子: 1. 添加依赖 在Maven项目中,需要添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 2. 配置Redis连接信息 在Spring Boot应用程序中,可以在application.properties或application.yml文件中配置Redis连接信息,例如: ``` spring.redis.host=localhost spring.redis.port=6379 spring.redis.password=123456 ``` 3. 编写Redis操作代码 在Spring Boot应用程序中,可以使用RedisTemplate或Spring Data Redis提供的Repository来操作Redis。以下是使用RedisTemplate操作Redis的例子: ``` @Service public class RedisService { @Autowired private RedisTemplate<String, String> redisTemplate; public void set(String key, String value) { redisTemplate.opsForValue().set(key, value); } public String get(String key) { return redisTemplate.opsForValue().get(key); } } ``` 在上面的代码中,我们通过注入RedisTemplate对象来操作Redis。set方法用于将key-value数据存储到Redis中,get方法用于从Redis中获取key对应的value值。 4. 测试 在Spring Boot应用程序中,可以编写单元测试代码来测试Redis操作是否正常。以下是一个简单的测试代码示例: ``` @RunWith(SpringRunner.class) @SpringBootTest public class RedisServiceTest { @Autowired private RedisService redisService; @Test public void testSetAndGet() { String key = "key1"; String value = "value1"; redisService.set(key, value); String result = redisService.get(key); Assert.assertEquals(value, result); } } ``` 在上面的代码中,我们通过注入RedisService对象来测试Redis操作是否正常。testSetAndGet方法用于测试set和get方法是否能够正常工作。 总之,Spring Data Redis是一个非常方便的模块,可以帮助开发人员快速集成RedisSpring Boot应用程序中,并提供了一组简单易用的API,使得开发人员可以方便地进行Redis操作

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值