瑞_Redis_Redis的Java客户端

🙊 前言:本文章为瑞_系列专栏之《Redis》的基础篇的Redis的Java客户端章节。由于博主是从B站黑马程序员的《Redis》学习其相关知识,所以本系列专栏主要是针对该课程进行笔记总结和拓展,文中的部分原理及图解等也是来源于黑马提供的资料,特此注明。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!


主机操作系统:Windows10
VMware版本: VMware Workstation 16.2.4
Linux版本:CentOS 7 64位
远程连接工具:MobaXterm_Personal_23.2
Redis版本:redis-6.2.6.tar.gz
Redis客户端:resp-2022.2.0.0

相关链接:《瑞_VMware虚拟机安装Linux纯净版(含卸载,图文超详细)》
相关链接:《瑞_Redis_初识Redis(含安装教程)》
相关链接:《瑞_Redis_Redis客户端》
相关链接:《瑞_Redis_Redis命令》

Ray-Logo

1 Redis的Java客户端

  在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/

在这里插入图片描述


  标记为❤的就是推荐使用的java客户端,包括⬇️

  • Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。
  • Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map.Queue等,而且支持跨进程的同步机制:Lock.Semaphore等待,比较适合用来实现特殊的功能需求。

在这里插入图片描述

Spring Data Redis 底层兼容了 Jedis 和 Lettuce



1.1 Jedis快速入门

  Jedis的官网地址:http://github.com/redis/jedis

1.1.1 入门案例

1.1.1.1 项目构建

  新建一个普通的 Maven 项目,项目名为jedis-demo,如下

在这里插入图片描述

1.1.1.2 引入依赖

  pom.xml引入依赖如下:

        <!--jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.7.0</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.0</version>
            <scope>test</scope>
        </dependency>

在这里插入图片描述

  完整的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ray.study.redis</groupId>
    <artifactId>jedis-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.7.0</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

瑞:随时时间的推移,该版本可能已经较为老旧(但适配JDK1.8)如果下载不到该依赖,可以参考下面的配置

  能下载到 jedis 3.7.0版本的镜像仓库的Maven配置

      <mirror>
        <id>nexus-aliyun</id>
        <mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
        <name>Nexus aliyun</name>
        <url>https://maven.aliyun.com/repository/public</url>
      </mirror>
1.1.1.3 建立连接

  新建一个单元测试类JedisTest,内容如下:

在这里插入图片描述

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

import java.util.Map;

/**
 * Jedis快速入门测试
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/1 21:59
 **/
public class JedisTest {

    private Jedis jedis;

    @BeforeEach
    void setUp() {
        // 1.建立连接(IP换成你Redis服务的IP)
        jedis = new Jedis("192.168.13.128", 6379);
        // 2.设置密码(如果密码不一样请记得替换)
        jedis.auth("123456");
        // 3.选择库
        jedis.select(0);
    }
}
1.1.1.4 释放资源

  单元测试类JedisTest中添加:

@AfterEach
void tearDown() {
    if (jedis != null) {
        jedis.close();
    }
}

瑞:释放连接千万不能忘,否则容易造成内存泄漏,内存泄漏的积累可能会导致内存溢出,后果非常严重

1.1.1.5 测试

  单元测试类JedisTest中编写测试代码:

@Test
void testString() {
    // 存入数据
    String result = jedis.set("name", "虎哥");
    System.out.println("result = " + result);
    // 获取数据
    String name = jedis.get("name");
    System.out.println("name = " + name);
}

@Test
void testHash() {
    // 插入hash数据
    jedis.hset("user:1", "name", "Jack");
    jedis.hset("user:1", "age", "21");

    // 获取
    Map<String, String> map = jedis.hgetAll("user:1");
    System.out.println(map);
}
1.1.1.6 完整测试类代码
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

import java.util.Map;

/**
 * Jedis快速入门测试
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/1 21:59
 **/
public class JedisTest {

    private Jedis jedis;

    @BeforeEach
    void setUp() {
        // 1.建立连接(IP换成你Redis服务的IP)
        jedis = new Jedis("192.168.13.128", 6379);
        // 2.设置密码(如果密码不一样请记得替换)
        jedis.auth("123456");
        // 3.选择库
        jedis.select(0);
    }

    @AfterEach
    void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
    }

    @Test
    void testString() {
        // 存入数据
        String result = jedis.set("name", "瑞神");
        System.out.println("result = " + result);
        // 获取数据
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testHash() {
        // 插入hash数据
        jedis.hset("user:1", "name", "Jack");
        jedis.hset("user:1", "age", "21");

        // 获取
        Map<String, String> map = jedis.hgetAll("user:1");
        System.out.println(map);
    }
}

  运行结果如下⬇️

在这里插入图片描述

瑞:
  如果连接不上Linux中的Redis服务,可以参考《附:解决Liunx防火墙和开放端口号》
  其它Redis数据类型的命令不演示,有需要可以参考《瑞_Redis_Redis命令》

1.1.2 Jedis连接池

  Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。

  有关池化思想,并不仅仅是这里会使用,很多地方都有,比如说我们的数据库连接池,比如我们 tomcat 中的线程池,这些都是池化思想的体现。

1.1.2.1 连接池工具类

瑞:以下代码基于本文1.1.1章节的项目

  创建JedisConnectionFactory连接池工具类

在这里插入图片描述

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Jedis 连接池工具类
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/1 22:32
 **/
public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 最大连接
        poolConfig.setMaxTotal(8);
        // 最大空闲连接
        poolConfig.setMaxIdle(8);
        // 最小空闲连接
        poolConfig.setMinIdle(0);
        // 设置最长等待时间,单位:ms
        poolConfig.setMaxWaitMillis(1000);
        // 创建连接池对象(记得替换为你Redis服务的IP,端口,密码)
        jedisPool = new JedisPool(poolConfig,
                "192.168.13.128",6379,1000,"123456");
    }

    // 获取Jedis对象
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

代码说明:

  • 1) JedisConnectionFacotry:工厂设计模式是实际开发中非常常用的一种设计模式,我们可以使用工厂,去降低代的耦合,比如Spring中的Bean的创建,就用到了工厂设计模式

瑞:工厂模式详情可以参考《瑞_23种设计模式_工厂模式》

  • 2)静态代码块:随着类的加载而加载,确保只能执行一次,我们在加载当前工厂类的时候,就可以执行static的操作完成对连接池的初始化
  • 3)最后提供返回连接池中连接的方法
1.1.2.2 改造原始代码

  对JedisTest类进行如下改造,测试方法不变

    @BeforeEach
    void setUp() {
        // 1.建立连接(IP换成你Redis服务的IP)
//        jedis = new Jedis("192.168.13.128", 6379);
        // 代码改造
        jedis = JedisConnectionFactory.getJedis();
        // 2.设置密码(如果密码不一样请记得替换)
        jedis.auth("123456");
        // 3.选择库
        jedis.select(0);
    }

    @AfterEach
    void tearDown() {
        if (jedis != null) {
            // 代码未修改,但是执行逻辑变为向连接池归还连接对象
            jedis.close();
        }
    }

在这里插入图片描述

  代码说明:

  在我们完成了使用工厂设计模式来完成代码的编写之后,我们在获得连接时,就可以通过工厂来获得。而不用直接去new对象,降低耦合,并且使用的还是连接池对象。

  当我们使用了连接池后,我们关闭连接其实并不是关闭,而是将Jedis连接对象还回连接池




1.2 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实现

SpringDataJpa使用起来非常简单,记住如下几个步骤即可

SpringDataRedis的使用步骤:

  • 引入spring-boot-starter-data-redis依赖
  • 在application.yml配置Redis信息
  • 注入RedisTemplate
1.2.1 RedisTemplate

  SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

API返回值类型说明
redisTemplate.opsForValue()ValueOperations操作String类型数据
redisTemplate.opsForHash()HashOperations操作Hash类型数据
redisTemplate.opsForList()ListOperations操作List类型数据
redisTemplate.opsForSet()SetOperations操作Set类型数据
redisTemplate.opsForZSet()ZSetOperations操作SortedSet类型数据
redisTemplate通用的命令
1.2.2 快速入门

  SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:

1.2.2.1 项目构建

  新建一个普通的 Maven 项目,项目名为springdataredis-demo,如下

在这里插入图片描述

1.2.2.2 引入依赖

  pom.xml引入依赖如下:

        <!--Redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency> 
        <!--连接池依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

在这里插入图片描述

  完整的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ray.study.redis</groupId>
    <artifactId>springdataredis-demo</artifactId>
    <version>1.0.0</version>
    <name>redis-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--common-pool-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--Jackson依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

1.2.2.3 配置application.yml文件
spring:
  redis:
    # Redis 服务IP
    host: 192.168.13.128
    # Redis 服务端口号
    port: 6379
    # Redis 服务密码
    password: 123456
    lettuce:
      pool:
        # 最大连接
        max-active: 8
        # 最大空闲连接
        max-idle: 8
        # 最小空闲连接
        min-idle: 0
        # 连接等待时间
        max-wait: 100ms 

在这里插入图片描述

1.2.2.4 创建启动类

  新建一个springboot启动类SpringDataRedisApplication,内容如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/1 23:47
 **/
@SpringBootApplication
public class SpringDataRedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringDataRedisApplication.class, args);
    }
}

在这里插入图片描述

1.2.2.5 测试

  新建一个单元测试类SpringDataRedisDemoApplicationTest,内容如下:

import com.ray.springdateredis.SpringDataRedisApplication;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * SpringDataRedis 测试类
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/1 23:41
 **/
@SpringBootTest(classes = SpringDataRedisApplication.class)
public class SpringDataRedisDemoApplicationTest {
    // 注入RedisTemplate
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void testString() {
        // 写入一条String数据
        redisTemplate.opsForValue().set("name", "瑞神");
        // 获取string数据
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }
}

在这里插入图片描述

1.2.3 数据序列化器

  SpringDataRedis的序列化方式:RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:

在这里插入图片描述
缺点:

  • 可读性差
  • 内存占用较大

瑞:我们希望写入什么即存入什么

1.2.3.1 自定义RedisTemplate序列化方式

  可以自定义RedisTemplate的序列化方式,创建配置类RedisConfig,代码如下:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer =
                new GenericJackson2JsonRedisSerializer();
        // 设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}

在这里插入图片描述

1.2.3.2 测试

  创建实体类User,如下:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;
}

在这里插入图片描述

  修改测试类SpringDataRedisDemoApplicationTest,如下:

    // 注入RedisTemplate
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Test
    void testSaveUser() {
        // 写入数据
        redisTemplate.opsForValue().set("user:100", new User("瑞神", 21));
        // 获取数据
        User o = (User) redisTemplate.opsForValue().get("user:100");
        System.out.println("o = " + o);
    }

  修改后完整测试类SpringDataRedisDemoApplicationTest代码

import com.ray.springdateredis.SpringDataRedisApplication;
import com.ray.springdateredis.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * SpringDataRedis 测试类
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/1 23:41
 **/
@SpringBootTest(classes = SpringDataRedisApplication.class)
public class SpringDataRedisDemoApplicationTest {
    // 注入RedisTemplate
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void testString() {
        // 写入一条String数据
        redisTemplate.opsForValue().set("name", "瑞神");
        // 获取string数据
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testSaveUser() {
        // 写入数据
        redisTemplate.opsForValue().set("user:100", new User("瑞神", 21));
        // 获取数据
        User o = (User) redisTemplate.opsForValue().get("user:100");
        System.out.println("o = " + o);
    }
}

这里采用了JSON序列化来代替默认的JDK序列化方式。最终结果如下图

在这里插入图片描述

  整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销

瑞:为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。这很不友好,在数据量增大之后会浪费较大没必要的内存,因为开发人员自身是知道字节码类型的。

1.2.4 StringRedisTemplate ★

  为了减少内存的消耗,我们可以采用手动序列化的方式,换句话说,就是不借助默认的序列化器,而是我们自己来控制序列化的动作,同时,我们只采用String的序列化器,这样,在存储value时,我们就不需要在内存中就不用多存储数据,从而节约我们的内存空间。

在这里插入图片描述

  由于这种用法比较普遍,所以SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。

在这里插入图片描述

  使用:省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用:

User类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;
}
RedisStringTest测试类
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ray.springdateredis.SpringDataRedisApplication;
import com.ray.springdateredis.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.Map;

/**
 * StringRedisTemplate 测试类
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2024/3/2 23:43
 **/
@SpringBootTest(classes = SpringDataRedisApplication.class)
class RedisStringTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testString() {
        // 写入一条String数据
        stringRedisTemplate.opsForValue().set("verify:phone:19123456789", "123456");
        // 获取string数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }

    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    void testSaveUser() throws JsonProcessingException {
        // 创建对象
        User user = new User("阿瑞", 21);
        // 手动序列化
        String json = mapper.writeValueAsString(user);
        // 写入数据
        stringRedisTemplate.opsForValue().set("user:200", json);

        // 获取数据
        String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
        // 手动反序列化
        User user1 = mapper.readValue(jsonUser, User.class);
        System.out.println("user1 = " + user1);
    }
    
    @Test
    void testHash() {
        stringRedisTemplate.opsForHash().put("user:400", "name", "瑞神");
        stringRedisTemplate.opsForHash().put("user:400", "age", "21");

        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
        System.out.println("entries = " + entries);
    }

}

在这里插入图片描述

瑞:ObjectMapper(Jackson) 是MVC默认的序列化工具,也可以使用阿里的 fastjson 等其它的序列化工具。

  此时再来看存储的数据,就会发现那个@class数据已经不在了,节约了我们的空间~

在这里插入图片描述

1.2.5 RedisTemplate总结

RedisTemplate的两种序列化实践方案:

  • 方案一:
    • 自定义RedisTemplate
    • 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
  • 方案二:
    • 使用StringRedisTemplate
    • 写入Redis时,手动把对象序列化为JSON
    • 读取Redis时,手动把读取到的JSON反序列化为对象

瑞:两种方案没有对错之分,各有优劣,不同场景下适合和更适合的区别




本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞486

你的点赞评论收藏才更是动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值