MapStruct应用实战及BeanUtils性能比较


1、MapStruct介绍

MapStruct官方文档

  1. MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类,它基于约定优于配置方法,极大地简化了 Java bean
    类型之间映射的实现。
  2. 您所要做的就是定义一个mapper接口,该接口声明任何所需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用普通的Java方法调用来在源对象和目标对象之间进行映射,即没有反射或类似。
  3. 与手工编写映射代码相比,MapStruct通过生成繁琐且易于编写的代码来节省时间。遵循约定优于配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时会采取措施。
  4. 与动态映射框架相比,MapStruct具有以下优势:
  • 通过使用普通方法调用而不是反射来快速执行

  • 编译时类型安全:只能映射相互映射的对象和属性,不会将订单实体意外映射到客户DTO等。

  • 在构建时清除错误报告,如果

    • 映射不完整(并非所有目标属性都已映射)
    • 映射不正确(找不到合适的映射方法或类型转换)

2、应用设置

2.1 Maven依赖

<?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.siayou</groupId>
    <artifactId>mapstruct-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.12</org.projectlombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <!-- lombok dependencies should not end up on classpath -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3、功能实战

3.1 常用注解

  • @Mapper 标记这个接口作为一个映射接口,并且是编译时 MapStruct 处理器的入口
  • @Mapping 解决源对象和目标对象中,属性名字不同的情况
  • @Mappings 当存在多个 @Mapping 需要配置;可以通过 @Mappings 批量指定
  • Mappers.getMapper Mapper 的 class 获取自动生成的实现对象,从而让客户端可以访问 Mapper 接口的实现

3.2 基本映射

3.2.1 定义映射器的Java接口
@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
    //mapper可以进行字段映射,改变字段类型,指定格式化的方式,包括一些日期的默认处理。
    //无论date转string,还是string转date,都是用dateFormat
    @Mapping(source = "gender.name", target = "gender")
    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVO student2StudentVO(Student student);
}
// @Data 在编译时会自动添加 Getter、Setter、equals、canEqual、hasCode、toString 等方法,高效且代码非常简洁。
// @Builder 可代替需要的很多构造函数,解决了某个类有很多构造函数的情况。
// @AllArgsConstructor 在编译时会自动添加一个含有所有已声明字段的构造函数,不必再手动编写含有所有已声明字段的构造函数。
// @NoArgsConstructor 在编译时会自动添加一个无参的构造函数,不必再手动编写无参构造函数。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private GenderEnum gender;
    //身高
    private Double height;
    //生日
    private Date birthday;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {

    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private String gender;
    //身高
    private Double height;
    //生日
    private String birthday;
}

public enum GenderEnum {
    Male("1", "男"),
    Female("0", "女");

    private String code;
    private String name;

    public String getCode() {
        return this.code;
    }

    public String getName() {
        return this.name;
    }

    GenderEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }
}
3.2.2 测试验证
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("张三")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .build();
        System.out.println(student);
        StudentVO studentVO = StudentMapper.INSTANCE.student2StudentVO(student);
        System.out.println(studentVO);
    }
    
 //测试结果
Student(name=张三, age=16, gender=Male, height=174.3, birthday=Fri Sep 22 16:22:38 CST 2023)
StudentVO(name=张三, age=16, gender=, height=174.3, birthday=2023-09-22 16:22:38)

3.3 参数引用映射

3.3.1 定义映射器的Java接口
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {
    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private String gender;
    //身高
    private Double height;
    //生日
    private String birthday;
    private ExtendDto extendDto;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private GenderEnum gender;
    //身高
    private Double height;
    //生日
    private Date birthday;

    private ExtendDto extendDto;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ExtendDto {
    //邮箱
    private String email;
    //地址
    private String address;
    //电话
    private String phone;
}

@Mapper
public interface StudentMapper {
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "email", target = "extendDto.email")
    StudentVO studentTwoStudentVO(Student student,String email);
}
3.3.2 测试验证
public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .build();
        System.out.println(student);
        StudentVO studentVO = StudentMapper.INSTANCE.studentTwoStudentVO(student,"dmjxsy@126.com");
        System.out.println(studentVO);
    }
    
 //测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:12:36 CST 2023, extendDto=null)
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:12:36, extendDto=ExtendDto(email=dmjxsy@126.com, address=null, phone=null))

3.4 多对象参数映射

3.4.1 定义映射器的Java接口
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = "extendDto")
    StudentVO studentMapStudentVO(Student student,ExtendDto extendDto);
3.4.2 测试验证
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .build();
        ExtendDto extendDto = ExtendDto.builder()
                .email("dmjxsy@126.com")
                .phone("119")
                .address("陕西")
                .build();
        System.out.println(student);
        System.out.println(extendDto);
        StudentVO studentVO = StudentMapper.INSTANCE.studentMapStudentVO(student,extendDto);
        System.out.println(studentVO);
}
 //测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:24:18 CST 2023, extendDto=null)
ExtendDto(email=dmjxsy@126.com, address=陕西, phone=119)
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:24:18, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=119))
    }
3.4.3 注意点

当配置了参数时,在配置对象属性关系映射时,也要显示的指明对象参数的名称。如@Mapping(source = “student.birthday”, target = “birthday”) 就显示指明了 source = “student.birthday” 。

3.5 嵌套映射

3.5.1 定义映射器的Java接口
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = ".")
    StudentVO studentMapStudentVO(Student student);
3.5.2 测试验证
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .extendDto( ExtendDto.builder()
                        .email("dmjxsy@126.com")
                        .phone("110")
                        .address("陕西")
                        .build())
                .build();
        System.out.println(student);
        StudentVO studentVO = StudentMapper.INSTANCE.studentMapStudentVO(student);
        System.out.println(studentVO);
    }
//测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:31:14 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:31:14, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))

3.5.3 注意点

使用 “ . ” ,将一个嵌套的bean的值合并到一个扁平化的对象中。

3.6 对象更新

3.6.1 定义映射器的Java接口
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = ".")
//    @Mapping(source = "name",target = "name",  ignore = true)
    void updateStudent(Student student,@MappingTarget StudentVO studentVO);
    
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = ".")
    @Mapping(source = "name",target = "name",  ignore = true)
    void updateStudent(Student student,@MappingTarget StudentVO studentVO);
3.6.2 测试验证
public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .extendDto( ExtendDto.builder()
                        .email("dmjxsy@126.com")
                        .phone("110")
                        .address("陕西")
                        .build())
                .build();
        StudentVO studentVO = StudentVO.builder()
                .name("王五")
                .age(20)
                .height(204.3)
                .birthday("2020-01-01 12:11:11")
                .extendDto( ExtendDto.builder()
                        .email("dmjxsy@163.com")
                        .phone("110110119")
                        .address("陕西西安")
                        .build())
                .build();
        System.out.println("更新前 student:"+student.toString());
        System.out.println("更新前 studentVO:"+studentVO.toString());
        StudentMapper.INSTANCE.updateStudent(student,studentVO);

        System.out.println("更新后 student:"+student.toString());
        System.out.println("更新后 studentVO:"+studentVO.toString());
    }
//测试结果
更新前 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:56:35 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新前 :StudentVO(name=王五, age=20, gender=null, height=204.3, birthday=2020-01-01 12:11:11, extendDto=ExtendDto(email=dmjxsy@163.com, address=陕西西安, phone=110110119))
更新后 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:56:35 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新后 :StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:56:35, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
//添加 @Mapping(source = "name",target = "name",  ignore = true)
更新前 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:57:39 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新前 :StudentVO(name=王五, age=20, gender=null, height=204.3, birthday=2020-01-01 12:11:11, extendDto=ExtendDto(email=dmjxsy@163.com, address=陕西西安, phone=110110119))
更新后 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:57:39 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新后 :StudentVO(name=王五, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:57:39, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))

3.6.3 注意点

@MappingTarget 使用改注解标记目标对象我们就可以更新该对象的值。如果想不更新某个值,可以给加一个ignore = true的标签来忽略。

4、性能比较

4.1 Apache BeanUtils 与 MapStruct比较

 public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 10; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                org.apache.commons.beanutils.BeanUtils.copyProperties(studentVO, student);
            }
            System.out.println("org.apache.commons.beanutils.BeanUtils第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
//测试结果
org.apache.commons.beanutils.BeanUtils0次执行---100W次转换耗时:11854
MapStruct0次执行-----100W次转换耗时:1981
org.apache.commons.beanutils.BeanUtils1次执行---100W次转换耗时:10828
MapStruct1次执行-----100W次转换耗时:2080
org.apache.commons.beanutils.BeanUtils2次执行---100W次转换耗时:9894
MapStruct2次执行-----100W次转换耗时:1891
org.apache.commons.beanutils.BeanUtils3次执行---100W次转换耗时:9543
MapStruct3次执行-----100W次转换耗时:1735
org.apache.commons.beanutils.BeanUtils4次执行---100W次转换耗时:6862
MapStruct4次执行-----100W次转换耗时:1958

4.2 Hutool BeanUtil与 MapStruct比较

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                cn.hutool.core.bean.BeanUtil.copyProperties(studentVO,student,true);
            }
            System.out.println("cn.hutool.core.bean.BeanUtil第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
//测试结果
cn.hutool.core.bean.BeanUtil0次执行---100W次转换耗时:7169
MapStruct0次执行-----100W次转换耗时:2538
cn.hutool.core.bean.BeanUtil1次执行---100W次转换耗时:4797
MapStruct1次执行-----100W次转换耗时:1707
cn.hutool.core.bean.BeanUtil2次执行---100W次转换耗时:5582
MapStruct2次执行-----100W次转换耗时:1505
cn.hutool.core.bean.BeanUtil3次执行---100W次转换耗时:4906
MapStruct3次执行-----100W次转换耗时:1322
cn.hutool.core.bean.BeanUtil4次执行---100W次转换耗时:3789
MapStruct4次执行-----100W次转换耗时:1635

4.3 Spring BeanUtils 与 MapStruct比较

    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                org.springframework.beans.BeanUtils.copyProperties(studentVO, student);
            }
            System.out.println("org.springframework.beans.BeanUtils第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
//测试结果
org.springframework.beans.BeanUtils0次执行---100W次转换耗时:1156
MapStruct0次执行-----100W次转换耗时:3376
org.springframework.beans.BeanUtils1次执行---100W次转换耗时:290
MapStruct1次执行-----100W次转换耗时:1367
org.springframework.beans.BeanUtils2次执行---100W次转换耗时:506
MapStruct2次执行-----100W次转换耗时:1586
org.springframework.beans.BeanUtils3次执行---100W次转换耗时:318
MapStruct3次执行-----100W次转换耗时:1319
org.springframework.beans.BeanUtils4次执行---100W次转换耗时:304
MapStruct4次执行-----100W次转换耗时:1226

4.4 Cglib BeanCopier 与 MapStruct比较

 public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                org.springframework.cglib.beans.BeanCopier.create(StudentVO.class, Student.class, false);
            }
            System.out.println("org.springframework.cglib.beans.BeanCopier第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
 //测试结果
org.springframework.cglib.beans.BeanCopier0次执行---100W次转换耗时:725
MapStruct0次执行-----100W次转换耗时:4524
org.springframework.cglib.beans.BeanCopier1次执行---100W次转换耗时:210
MapStruct1次执行-----100W次转换耗时:1663
org.springframework.cglib.beans.BeanCopier2次执行---100W次转换耗时:72
MapStruct2次执行-----100W次转换耗时:1446
org.springframework.cglib.beans.BeanCopier3次执行---100W次转换耗时:80
MapStruct3次执行-----100W次转换耗时:1482
org.springframework.cglib.beans.BeanCopier4次执行---100W次转换耗时:84
MapStruct4次执行-----100W次转换耗时:1441

4.4 性能对比结果(5次平均值)

工具类执行1000执行1w执行10w执行100w
Apache BeanUtils103.2323.31105.87227.8
Hutool BeanUtil111249.2925.44420.6
Spring BeanUtils8093.4206.4728.6
Cglib BeanCopier86.4103.8103.8263
MapStruct53.6137.8447.82672.2
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MapStructBeanUtils是Java中常用的对象映射工具,它们都可以用于将一个对象的值映射到另一个对象上。以下是它们的优缺点: MapStruct的优点: 1. 性能优秀:MapStruct在编译期间生成映射代码,相比运行时的反射机制,具有更好的性能表现。 2. 类型安全:MapStruct在编译期间进行类型检查,避免了在运行时可能出现的类型转换错误。 3. 易于使用:MapStruct通过注解配置简单明了,生成的映射代码也易于理解和维护。 MapStruct的缺点: 1. 学习曲线较陡:对于初学者来说,需要一定时间去了解和掌握MapStruct的使用方式和配置方式。 2. 配置复杂:对于复杂的映射场景,可能需要编写自定义的转换器或者使用复杂的配置方式。 BeanUtils的优点: 1. 简单易用:BeanUtils提供了简单的API,易于学习和使用。 2. 动态性:BeanUtils使用反射机制,在运行时可以动态地进行属性复制。 BeanUtils的缺点: 1. 性能较差:由于使用了反射机制,BeanUtils在属性复制过程中性能相对较低,特别是处理大量对象时会有明显的性能损耗。 2. 不支持类型安全:BeanUtils在属性复制时没有类型检查,容易出现类型转换错误。 综上所述,MapStruct性能和类型安全方面具有优势,适用于需要高性能和类型安全的场景。而BeanUtils则更适用于简单的属性复制场景,对于性能要求不高且不涉及复杂类型转换的情况下使用较为方便。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值