Java对象转换最佳方案

全网详细解说:java对象转JSONObject和JSONString、JSONObject转java对象和JSONString,JSONString转JSONObject和java对象 阅读详情

系统变的复杂,系统的层次划分越来越细,边界也越来越明确。 然后每一层之间一般都有自己要处理的领域对象,统称为pojo一般在model或者domain包下(类的后缀不能为pojo)。

常见的一些模型类型:

  • PO、DO:持久层对象,一般和数据库直接打交道。
  • DTO:数据传输对象,系统之间的交互,再服务层提供服务的时候输出到其它系统。
  • VO:视图对象,用于前端模型展示。 当然有时候前端也可以看做另外一个系统,使用DTO模型;
  • BO:业务逻辑对象,比较少用...

为什么模型要分这么多层?

在复杂一点的业务中,业务建模是非常有必要的,一定要抽象出业务上常用的领域模型,统一技术和非技术同学的语言。

建完模型之后,在技术的系统中,为了方便维护代码,分离关注点,也会进行再次分层,让每一层解决特定的问题。模型的分层是随着系统的分层而来的;试想所有的模型属性在一个对象中,这个对象你看的懂吗?

举个实际的案例:

  • 数据层一般用DO

  • 现在要透出数据给其他系统,DO中一般都会有创建人,创建时间,修改人,修改时间,当前对象所处的环境等信息; 但是外部的系统需要环境、创建人信息吗? 很多时候不需要,站在数据安全的角度,一般只透出必要的字段就可以; 这些要输出要外部系统的必要字段,一般定义在DTO中。

  • 到前端系统,前端系统展示上所需的逻辑和输出到外部系统的又有点不太一样,前端系统可能要创建人,创建时间,但是不要另外一些东西,或者一些敏感的配置不能透出给前端,这个时候一般也会再定义一个新的对象。

简单说就是当我们的系统要输出能力到外部系统的时候,不同系统要的数据不一样,数据安全要求我们不能透出这么多的数据,一定要做一层处理。 另外给另外一个系统关注的数据,而不是一股脑的全部都给对方,对方处理起来也方便。

模型之间的转换

建议不要用的方式

  • 手写get\set; 虽然性能高,但是费劲并且眼花缭乱,一不小心就写错了,难以维护,复用度不高
  • BeanUtils,apacha和spring包下都有对应的类,但是底层用到的都是反射,性能比较差,大流量的情况下一般不用
  • 直接fastjson,gc会很频繁,而且性能比较差

常用的方式

  • cglib的beanCopier,开销在创建BeanCopier,一般在创建类的时候提前创建好一个,在代码运行的时候直接进行copy,性能接近原生。
  • mapstruct 性能和原生代码一样,支持复杂的转化场景,实现原理同lombok编译的时候生成对应的代码。

以上从技术分类的角度来看:

  • 反射:fastjson,beanutil 都不建议用
  • get\set: beancoper通过字节码进行getset,mapstruct编译的时候生成getset。 性能相对较好。

使用方式

个人觉得,如果说对象比较简单的时候,使用BeanCopier就可以了,因为spring的aop依赖cglib,默认情况下就已经引入了对应的包了,不需要额外的依赖直接就可以用。

如果很复杂的模型之间的转换,并且对性能有更极致的要求,考虑使用下MapStruct。

定义对象

UserDO

@Data
public class UserDO {

  private Long id;

  private String name;

  private Integer gender;

  private String password;

  private Date gmtCreate;

  private Date gmtModified;

}

UserDTO

@Data
public class UserDTO {

  private Long id;

  private String name;

  private Integer gender;

}

BeanCopier

最简单的使用方式

BeanCopier beanCopier = BeanCopier.create(UserDO.class, UserDTO.class, false); bean.copy即可;

private static void simpleBeanCopy() {
    BeanCopier beanCopier = BeanCopier.create(UserDO.class, UserDTO.class, false);
    UserDO userDO = new UserDO();
    userDO.setId(1L);
    userDO.setName("aihe");
    userDO.setGmtCreate(new Date());
    userDO.setGender(0);
    userDO.setPassword("xxxxxx");
    UserDTO userDTO = new UserDTO();
    beanCopier.copy(userDO, userDTO,null);
    Assert.assertEquals("名称未成功拷贝",userDTO.getName(),"aihe");
    Assert.assertEquals("Id未成功拷贝", 1L, (long)userDTO.getId());
    Assert.assertEquals("性别未成功拷贝", Integer.valueOf(0),userDTO.getGender());
  }

创建可复用的BeanCopier工具类

package me.aihe.daka;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import net.sf.cglib.beans.BeanCopier;

/**
 * @author : aihe 
 * @date : 2022/9/12 9:21 AM
 * 使用场景:
 * 功能描述:
 */
public class BeanCopyUtils {

    /**
     * beanCopier缓存
     * 由sourceClass和targetClass可以确定一个唯一的BeanCoper,因此使用二级Map;
     */
    private static Map<Class<?>, Map<Class<?>, BeanCopier>> beanCopierMap = new ConcurrentHashMap<>();


    /**
     * 直接指定Bean对象进行拷贝
     * @param sourceBean
     * @param targetBean
     * @param <S>
     * @param <T>
     */
    public static <S,T> void copy(S sourceBean,T targetBean){
        @SuppressWarnings("unchecked")
        Class<S> sourceClass = (Class<S>) sourceBean.getClass();
        @SuppressWarnings("unchecked")
        Class<T> targetClass = (Class<T>) targetBean.getClass();

        BeanCopier beanCopier = getBeanCopier(sourceClass,targetClass);
        beanCopier.copy(sourceBean,targetBean,null);
    }

    /**
     * 转换方法
     * @param sourceBean 原对象
     * @param targetClass 目标类
     * @param <S>
     * @param <T>
     * @return
     */
    public static <S,T> T convert(S sourceBean,Class<T> targetClass){
        try {
            assert sourceBean!=null;
            T targetBean = targetClass.newInstance();
            copy(sourceBean,targetBean);
            return targetBean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    private static <S,T> BeanCopier getBeanCopier(Class<S> sourceClass, Class<T> targetClass ){
        Map<Class<?>,BeanCopier> map = beanCopierMap.get(sourceClass);
        if(map == null || map.isEmpty()){
            BeanCopier newBeanCopier = BeanCopier.create(sourceClass, targetClass, false);
            Map<Class<?>,BeanCopier> newMap = new ConcurrentHashMap<>();
            newMap.put(targetClass,newBeanCopier);
            beanCopierMap.put(sourceClass,newMap);
            return newBeanCopier;
        }

        BeanCopier beanCopier = map.get(targetClass);
        if(beanCopier == null){
            BeanCopier newBeanCopier = BeanCopier.create(sourceClass, targetClass, false);
            map.put(targetClass,newBeanCopier);

            return newBeanCopier;
        }

        return beanCopier;
    }
}

 同上:

UserDO userDO = new UserDO();
    userDO.setId(1L);
    userDO.setName("aihe");
    userDO.setGmtCreate(new Date());
    userDO.setGender(0);
    userDO.setPassword("xxxxxx");
    UserDTO userDTO = new UserDTO();
    BeanCopyUtils.copy(userDO, userDTO);
    Assert.assertEquals("名称未成功拷贝",userDTO.getName(),"aihe");
    Assert.assertEquals("Id未成功拷贝", 1L, (long)userDTO.getId());
    Assert.assertEquals("性别未成功拷贝", Integer.valueOf(0),userDTO.getGender());

MapStruct

案例集: github.com/mapstruct/m…

引入mapstruct

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.20</org.projectlombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <!-- IntelliJ does not pick up the processor if it is not in the dependencies.
             There is already an open issue for IntelliJ see https://youtrack.jetbrains.com/issue/IDEA-150621
            -->
            <scope>provided</scope>
        </dependency>
       
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

 

简单Demo

定义Mapper

@Mapper
public interface UserDTOMapper {

    UserDTOMapper MAPPER = Mappers.getMapper( UserDTOMapper.class );

    //@Mapping( source = "test", target = "testing" )
    //@Mapping( source = "test1", target = "testing2" )
    UserDTO toTarget( UserDO s );
}

使用:

public static void main( String[] args ) {
        //simpleDemo();
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setName("aihe");
        userDO.setGmtCreate(new Date());
        userDO.setGender(0);
        userDO.setPassword("xxxxxx");
        UserDTO userDTO = UserDTOMapper.MAPPER.toTarget(userDO);
        Assert.assertEquals("名称未成功拷贝",userDTO.getName(),"aihe");
        Assert.assertEquals("Id未成功拷贝", 1L, (long)userDTO.getId());
        Assert.assertEquals("性别未成功拷贝", Integer.valueOf(0),userDTO.getGender());
    }

 常见用法

最后

总结下本文的内容:

  • 属性类型相同,名称不同的时候,使用@Mapping注解指定source和target字段名称对应关系, 如果有多个这种属性,那就指定多个@Mapping注解。
  • 忽略某个字段,在@Mapping的时候,加上ignore = true
  • 转化日期格式,字符串到数字的格式,可以使用dateFormat,numberFormat
  • 如果有自定义转换的需求,写一个简单的Java类即可,然后在方法上打上Mapstruct的注解@Named,在在@Mapper(uses = 自定义的类),然后@Mapping中用上qualifiedByName。
  • @Mapping(target = "userNick1", source = "userNick")
    @Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd")
    @Mapping(target = "age", source = "age", numberFormat = "#0.00")
    @Mapping(target = "id", ignore = true)
    @Mapping(target = "userVerified", defaultValue = "defaultValue-2")
    UserDTO toTarget( UserDO s );

    性能测试

    测试代码

    import java.util.Date;
    
    import com.alibaba.fastjson.JSON;
    
    import org.junit.Before;
    import org.junit.Test;
    
    /**
     * @author : aihe aihe.ah@alibaba-inc.com
     * @date : 2022/9/12 9:47 AM
     * 使用场景:
     * 功能描述:
     */
    public class BenchDemoTest{
    
      /**
       * 转化对象
       */
      private UserDO userDO;
    
      /**
       * 转化次数
       */
      private final static int count = 1000000;
    
    
      @Before
      public void before() {
        userDO = new UserDO();
        userDO.setId(1L);
        userDO.setName("aihe");
        userDO.setGmtCreate(new Date());
        userDO.setGender(0);
        userDO.setPassword("xxxxxx");
      }
    
    
      @Test
      public void mapstruct() {
        long startTime = System.currentTimeMillis();
        for (int i = 1; i <=count; i++) {
          UserDTO userDTO = UserDTOMapper.MAPPER.toTarget(userDO);
        }
        System.out.println("mapstruct time" + (System.currentTimeMillis() - startTime));
      }
    
      @Test
      public void beanCopier() {
        long startTime = System.currentTimeMillis();
        for (int i = 1; i <= count; i++) {
          UserDTO targetBean = new UserDTO();
          BeanCopyUtils.copy(userDO, targetBean);
        }
        System.out.println("beanCopier time" + (System.currentTimeMillis() - startTime));
      }
    
      @Test
      public void springBeanUtils(){
        long startTime = System.currentTimeMillis();
        for (int i = 1; i <=count; i++) {
          UserDTO userDTO = new UserDTO();
          org.springframework.beans.BeanUtils.copyProperties(userDO, userDTO);
        }
        System.out.println("springBeanUtils time" + (System.currentTimeMillis() - startTime));
      }
    
      @Test
      public void fastjson() {
        long startTime = System.currentTimeMillis();
        for (int i = 1; i <= count; i++) {
          JSON.parseObject(JSON.toJSONString(userDO), UserDTO.class);
        }
        System.out.println("fastjson time" + (System.currentTimeMillis() - startTime));
      }
    
    }
    

    测试结果

  • 可以看出BeanCopier和MapStruct是远远超过其他转换方式的...
  • BeanCopier虽然快,但是比mapstruct还是有20倍的性能差距...
  • 软件系统一般都会进行分层,领域模型也会随之进行分层,即每层都有自己关注的模型对象; 分层的主要原因是便于维护。
  • 模型之间的对象经常要互相转换,常用的转换实现有反射和get/set,反射的性能很差不建议使用
  • 然后写了基于get/set实现的beancopier和mapstruct使用方式,简单测试了下性能,mapstrcut优于其它各种对象转换方式。并且MapStrcut支持功能更加复杂的对象转换。 性能又好,功能又强大,所以可以考虑优先使用.

 

Redis 与 Java 的序列化问题:如何选择最佳方案? 在现代软件开发中,Redis 作为一款高性能的内存数据库,常与 Java 语言结合使用。而在 Java 与 Redis 交互过程中,序列化是一个关键环节。本文的目的就是深入探讨 Redis 与 Java 的序列化问题,分析各种序列化方案的优缺点,帮助开发者选择最适合自己项目的序列化方案。范围涵盖了常见的 Java 序列化方式,以及它们在 Redis 环境下的应用。本文将首先介绍核心概念,包括序列化、反序列化等,让读者对相关概念有清晰的认识。然后讲解核心算法原理和具体操作步骤,给出代码示例。 阅读详情

相关推荐

用Python实现一个简单的远程桌面服务端和客户端

本文介绍了一个基于Python的轻量级远程桌面解决方案,针对商业软件在嵌入式系统上的兼容性问题、性能问题和成本问题。系统采用客户端-服务端架构,包含两个通信通道:5000端口传输压缩视频流,5001端口传输控制事件。通过多线程设计、图像压缩(JPEG+zlib)和网络优化(禁用Nagle算法)等技术手段提高性能。服务端实现了高效的屏幕捕获(使用mss库)、图像处理流水线和事件处理逻辑。客户端采用PyQt5构建UI界面,包含视频接收线程以减少主线程负担。该方案具有跨平台、开源免费、轻量级(核心代码不到500行

Hi20240217的博客 1665

java 如何将一个对象转换成另一个对象

对于复杂的对象转换,可以使用像 Dozer 或 ModelMapper 这样的转换框架。导入Dozer的依赖和配置// 假设已有Dozer的依赖和配置// ...// ...//第一个参数是源对象,第二个参数是目标对象。

xiaotiaoza的博客 3201

Linux之SUSE系统SAP-HANA经常系统内存不足使得SAP应用不能使用解决方案

问题描述 Linux内存使用量超过阈值,使得sap应用程序无可用内存,最终导致程序崩溃。 问题原因 自己查阅了一些资料,也基于zabbix的可用内存和实际不太一样入手去找原因发现问题出现于Cached的值过大,导致系统没有可以再分配的内存空间。Cached只要用来缓存文件的,经常读写的文件会被缓存到Cached中,可以增加读写效率,该功能是Linux系统内核提供的,而SAP-HANA的日志文件很大...

liuxiangyang_的博客 6712

java对象相互转化多种实现方式

方式一:MapStruct 是一个代码生成器,它基于约定优于配置的方法,极大地简化了 Java bean 类型之间的映射实现。 生成的映射代码使用普通的方法调用,因此速度快、类型安全且易于理解。 官方文档 https://mapstruct.org/documentation/installation/ 更多使用示例 https://github.com/kevinLuan/mapstruct-examples mapper-struct使用比较灵活,性能非常出色。由于它的实现是通过在开发中生成源代码的

kevin_Luan的专栏 4978

基于python+mediapipe的手势拖动识别(无gpu)

手势点方块拖动

weixin_38029956的博客 5983

基于江协科技STM32创建HAL库工程模板

基于江协科技STM32入门教程创建的HAL库工程模板

2401_82728381的博客 1136

Vo对象,Dao对象,Dto对象的部分属性复制BeanCopier&BeanUtils

在实际的开发中,存在Vo对象,Dao对象,DTO对象的部分属性复制的问题 比较笨的办法 A.setxxx(B.getXXX),费时又费力,怎么解决这个问题呢? 推荐使用两种对象属性复制的办法: 对象属性拷贝的两种方式: 1、BeanUtils(注意BeanUtils使用的是spring的Beanutils的) 2、BeanCopier 先准备三个Vo对象,Dao对象,Dto对象,后面我们对这三个对象进行拷贝和复制。 @Data @AllArgsConstructor public class UserDo

keyboard专栏 1069

MiniQMT国债逆回购策略Python代码全解析

通过本文的介绍,我们不仅学习了如何在MiniQMT平台上使用Python编写国债逆回购策略,还对策略的逻辑构建、代码实现以及风险管理有了深入的了解。国债逆回购策略是一种适合初学者的低风险投资方式,希望本文能帮助你在量化交易的道路上迈出坚实的一步。

木头大左的博客 1919

java arraylist hashmap_Java将ArrayList映射到HashMap - java

我给页面提供了ArrayList ,其中每个文档都有一个称为type的属性。我不知道唯一类型或文档的数量。我想将此ArrayList排序为HashMap ,但是在解决它时遇到了一些麻烦。一些伪代码想for (int i = 0; i < documents.size(); i++){if there is an array for documents[i].typeadd to this a...

weixin_34502341的博客 401

CONN工具箱分析fmri数据笔记1

前言一、安装二、预处理三、去噪 前言 CONN:functional connectivity toolbox,是基于MATLAB和SPM的一个对功能磁共振数据进行分析的一个工具箱。可以进行以下分析: Seed-based correlations (SBC), ROI-to-ROI correlations, graph analyses, Independent component analyses(group-ICA), Masked (ICA) Amplitude of Low-Frequency.

weixin_52073820的博客 6587

Qt版Dalsa线扫相机的采集Demo

Qt Dalsa

追赶时代的博客 605

java date 序列化,Java中的序列化日期

I'm passing around some objects through web service and some of them contain java.sql.Date. Because Date doesn't have empty constructor it doesn't want to get serialized.First part of a question is ea...

weixin_39827625的博客 299

CEEMDAN算法及其应用

本博文主要介绍CEEMDAN算法的原理及其在信号去噪方面的应用

总结论文中的算法并给出论文的复现代码 6万+

四种bean拷贝工具对比

在日常编码中,经常会遇到DO、DTO对象之间的转换,如果对象本身的属性比较少的时候,那么我们采用硬编码手工setter也还ok,但如果对象的属性比较多的情况下,手工setter就显得又low又效率又低。这个时候我们就考虑采用一些工具类来进行对象属性的拷贝了。从测试结果中很明显可以看出采用HardCode方式进行对象属性Copy性能最佳;采用net.sf.cglib.beans.BeanCopier#copy方式进行对象属性copy性能最稳定;...

qq_37436172的博客 2645

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

java string 转对象,将String转换为类对象

I am storing a class object into a string using toString() method. Now, I want to convert the string into that class object.How to do that? Please help me with source code.解决方案I am storing a class obj...

weixin_29267307的博客 3227

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Java对象的拷贝转换方式探讨

关于Java对象直接互相转换的方式的一些探讨

qq_59620850的博客 592

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

java 对象转 soap,soap文件转换成java对象解决办法

soap文件转换成java对象怎么把下面的一个soap报文转换成一个java对象,想把ns1:requesthead转换成Requesthead对象,然后就包含了一些属性,如ns1:bizcode,还要把s1:Body也是转成Body对象,用java怎么写biz001tran0022012-11-2118:00:00reqid003respid004sign005v1.11E02_001A...

weixin_34006342的博客 1136

Drools Planner(第1.2章节)

第一章 Planner introduction(规划师介绍第1.2章节) 版本:5.5.0.Final 1.2 什么是规划问题? 1.2.1 规划问题是NP-complete 上述所有的用例可能是NP-complete[http://en.wikipedia.org/wiki/NP-complete].通俗地说,这儿意味着: a.在合理的时间内很容易用一个给定的解决方案去验证一

不了了之专栏 993

java 转换 xml,Java到XML的转换?

What are different approaches to convert Java Objects to XML, I know one option is JAXB but would like to know what are the other approaches/tools available for the same ?Note: I do not have further ...

weixin_42513269的博客 153

JAVA中将数组清空_清空Java中数组的最佳方法 - java

我有班级的数组MyClass[] data;设定长度:data = new MyClass[1];添加数据:data[0] = new MyClass();现在我需要清除数组。做这个的最好方式是什么?将null分配给该数组可以吗?data=null;参考方案您是否希望数组仍然存在但其中什么也没有?重新初始化数组:data = new MyClass[1];您是否希望数组不再存在,以便可以在JVM感...

weixin_42432918的博客 6709

在java中,将ArrayList转换为字符串String的最佳方法

在Java 8或更高版本中: String listString = String.join(", ", list); Returns a new String composed of copies of the {@code CharSequence elements} joined together with a copy of the specified {@code delimiter}...

qq_22583191的博客 6042

Mem Reduct 内存监控工具快速切换中文界面的完整设置指南

打开 Mem Reduct 的设置窗口,满屏都是英文:Language、Always on Top、Reduct Mask……想找"哪里能切语言",得先在菜单里扒拉半天。这篇文章就干一件事:把 Mem Reduct 的界面设置成中文,单台机器三步搞定;要是你手里管着一堆办公电脑,也给你留了配置文件和脚本两条批量路子。 ## 主界面语言下拉菜单,一步切中文 这是最省事的路线,适合"看完就去点"的

gitblog_00771的博客 763

阿里巴巴fastjson中对象转Sting字符串时,自动将首字母大写转小写的最佳解决方案

前言: fastjson由于高性能与简洁的API,受到了国内用户的欢迎,但是如果你碰到一个特殊的场景:java对象转String字符串时如何对象的字段首字母的大写会自动转成小写,这个设计本身没有什么问题,但是由于业务的情况不一样,通常需要就需要原始的field,不需要大小写,本人就是因为第三方接口的首字母必须是大写才遇到了这种情况,下面来说说解决方案。 解决方案 方案一: 在getting方法中使用注解 @JSONField(name="YouName") 适合场景:一个对象的只有少部分字段fie

weixin_48470176的博客 1583
上一篇: 神操!精选JVM垃圾回收机制全面分析,聊聊你眼中的JVM
下一篇: 面试被吊打!正确打开Redis分布式锁的七种方案,涨见识了
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值