Jackson工具类使用及配置指南、高性能配置

有些配置可能已经过时,具体配置清查考自己使用的Jackson版本

1、Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character was expecting double错误

解决fasterxml中string字符串转对象json格式错误问题。

springboot中jackson使用的包是fasterxml的。可以通过如下代码,将一个形如json格式string转为一个java对象:

com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
mapper.readValue(字符串, javabean.class);

但是,当我们要转的字符串是这种格式的就会报错,因为这种格式并不是规范的json格式:

{success:2,message:'认证已通过!设备未添加,请与和航联系。设备编号:1101'}

错误信息是:

com.fasterxml.jackson.core.JsonParseException: Unexpected character ('s' (code 115)): was expecting double-quote to start field name

意思就是:fasterxml期望字段名带有双引号,也就是期望是这样的:

{"success":2,"message":"认证已通过!设备未添加,请与和航联系。设备编号:1101"}

通过对比,发现上面的字符串和规范的json字符串主要有两个不同点

  1. 字段名未用引号
  2. 第二个是使用了单引号

这都是不规范的Json格式写法。 当然,最好的解决方式是将string字符串转成规范的json格式,但是由于某些原因,不得不使用这种格式怎么办呢?

解决:

com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
//允许使用未带引号的字段名
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
//允许使用单引号
mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);

mapper.readValue(字符串, javabean.class);

除了ALLOW_UNQUOTED_FIELD_NAMES,ALLOW_SINGLE_QUOTES还有其它的设置,有用到试试。

2、Jackson使用工具类

通常,我们对JSON格式的数据,只会进行解析和封装两种,也就是JSON字符串--->Java对象以及Java对象--->JSON字符串

public class JsonUtils {
    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

    private final static ObjectMapper objectMapper = new ObjectMapper();

    static {
        objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        objectMapper.configure(JsonParser.Feature.INTERN_FIELD_NAMES, true);
        objectMapper.configure(JsonParser.Feature.CANONICALIZE_FIELD_NAMES, true);
        objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    private JsonUtils() {
    }

    public static String encode(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonGenerationException e) {
            logger.error("encode(Object)", e); //$NON-NLS-1$
        } catch (JsonMappingException e) {
            logger.error("encode(Object)", e); //$NON-NLS-1$
        } catch (IOException e) {
            logger.error("encode(Object)", e); //$NON-NLS-1$
        }
        return null;
    }

    /**
     * 将json string反序列化成对象
     *
     * @param json
     * @param valueType
     * @return
     */
    public static <T> T decode(String json, Class<T> valueType) {
        try {
            return objectMapper.readValue(json, valueType);
        } catch (JsonParseException e) {
            logger.error("decode(String, Class<T>)", e);
        } catch (JsonMappingException e) {
            logger.error("decode(String, Class<T>)", e);
        } catch (IOException e) {
            logger.error("decode(String, Class<T>)", e);
        }
        return null;
    }

    /**
     * 将json array反序列化为对象
     *
     * @param json
     * @param jsonTypeReference
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T decode(String json, TypeReference<T> typeReference) {
        try {
            return (T) objectMapper.readValue(json, typeReference);
        } catch (JsonParseException e) {
            logger.error("decode(String, JsonTypeReference<T>)", e);
        } catch (JsonMappingException e) {
            logger.error("decode(String, JsonTypeReference<T>)", e);
        } catch (IOException e) {
            logger.error("decode(String, JsonTypeReference<T>)", e);
        }
        return null;
    }

}

3、Jackson配置属性

如果上面的工具类实例,在Jackson中存在一些属性配置,这些配置决定了最后在解析或者编码后数据视图。因此,在分析Jackson之前,先了解下,Jackson具有的一些配置含义。

3.1、JsonParser解析相关配置属性

JsonParser将JSON数据格式的String字符串,解析成为Java对象。Jackson在解析的时候,对于一些非JSON官方文档支持的属性,则需要通过一些配置才可以被Jackson工具解析成对象。

    /**
     * Enumeration that defines all togglable features for parsers.
     */
    public enum Feature {

        // // // Low-level I/O handling features:

        /**
         * 这个特性,决定了解析器是否将自动关闭那些不属于parser自己的输入源。 如果禁止,则调用应用不得不分别去关闭那些被用来创建parser的基础输入流InputStream和reader;
         * 如果允许,parser只要自己需要获取closed方法(当遇到输入流结束,或者parser自己调用 JsonParder#close方法),就会处理流关闭。
         *
         * 注意:这个属性默认是true,即允许自动关闭流
         *
         */
        AUTO_CLOSE_SOURCE(true),

        // // // Support for non-standard data format constructs

        /**
         * 该特性决定parser将是否允许解析使用Java/C++ 样式的注释(包括'/'+'*' 和'//' 变量)。 由于JSON标准说明书上面没有提到注释是否是合法的组成,所以这是一个非标准的特性;
         * 尽管如此,这个特性还是被广泛地使用。
         *
         * 注意:该属性默认是false,因此必须显式允许,即通过JsonParser.Feature.ALLOW_COMMENTS 配置为true。
         *
         */
        ALLOW_COMMENTS(false),

        /**
         * 这个特性决定parser是否将允许使用非双引号属性名字, (这种形式在Javascript中被允许,但是JSON标准说明书中没有)。
         *
         * 注意:由于JSON标准上需要为属性名称使用双引号,所以这也是一个非标准特性,默认是false的。
         * 同样,需要设置JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES为true,打开该特性。
         *
         */
        ALLOW_UNQUOTED_FIELD_NAMES(false),

        /**
         * 该特性决定parser是否允许单引号来包住属性名称和字符串值。
         *
         * 注意:默认下,该属性也是关闭的。需要设置JsonParser.Feature.ALLOW_SINGLE_QUOTES为true
         *
         */
        ALLOW_SINGLE_QUOTES(false),

        /**
         * 该特性决定parser是否允许JSON字符串包含非引号控制字符(值小于32的ASCII字符,包含制表符和换行符)。 如果该属性关闭,则如果遇到这些字符,则会抛出异常。
         * JSON标准说明书要求所有控制符必须使用引号,因此这是一个非标准的特性。
         *
         * 注意:默认时候,该属性关闭的。需要设置:JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS为true。
         *
         */
        ALLOW_UNQUOTED_CONTROL_CHARS(false),

        /**
         * 该特性可以允许接受所有引号引起来的字符,使用‘反斜杠\’机制:如果不允许,只有JSON标准说明书中 列出来的字符可以被避开约束。
         *
         * 由于JSON标准说明中要求为所有控制字符使用引号,这是一个非标准的特性,所以默认是关闭的。
         *
         * 注意:一般在设置ALLOW_SINGLE_QUOTES属性时,也设置了ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER属性,
         * 所以,有时候,你会看到不设置ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER为true,但是依然可以正常运行。
         *
         * @since 1.6
         */
        ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER(false),

        /**
         * 该特性决定parser是否允许JSON整数以多个0开始(比如,如果000001赋值给json某变量,
         * 如果不设置该属性,则解析成int会抛异常报错:org.codehaus.jackson.JsonParseException: Invalid numeric value: Leading zeroes not
         * allowed)
         *
         * 注意:该属性默认是关闭的,如果需要打开,则设置JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS为true。
         *
         * @since 1.8
         */
        ALLOW_NUMERIC_LEADING_ZEROS(false),

        /**
         * 该特性允许parser可以识别"Not-a-Number" (NaN)标识集合作为一个合法的浮点数。 例如: allows (tokens are quoted contents, not including
         * quotes):
         * <ul>
         * <li>"INF" (for positive infinity), as well as alias of "Infinity"
         * <li>"-INF" (for negative infinity), alias "-Infinity"
         * <li>"NaN" (for other not-a-numbers, like result of division by zero)
         * </ul>
         */

        ALLOW_NON_NUMERIC_NUMBERS(false),

        // // // Controlling canonicalization (interning etc)

        /**
         * 该特性决定JSON对象属性名称是否可以被String#intern 规范化表示。
         *
         * 如果允许,则JSON所有的属性名将会 intern() ;如果不设置,则不会规范化,
         *
         * 默认下,该属性是开放的。此外,必须设置CANONICALIZE_FIELD_NAMES为true
         *
         * 关于intern方法作用:当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串 (该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String
         * 对象添加到池中, 并且返回此 String 对象的引用。
         *
         * @since 1.3
         */
        INTERN_FIELD_NAMES(true),

        /**
         * 该特性决定JSON对象的属性名称是否被规范化。
         *
         * @since 1.5
         */
        CANONICALIZE_FIELD_NAMES(true),

        ;

        final boolean _defaultState;

        /**
         * Method that calculates bit set (flags) of all features that are enabled by default.
         */
        public static int collectDefaults() {
            int flags = 0;
            for (Feature f : values()) {
                if (f.enabledByDefault()) {
                    flags |= f.getMask();
                }
            }
            return flags;
        }

        private Feature(boolean defaultState) {
            _defaultState = defaultState;
        }

        public boolean enabledByDefault() {
            return _defaultState;
        }

        public boolean enabledIn(int flags) {
            return (flags & getMask()) != 0;
        }

        public int getMask() {
            return (1 << ordinal());
        }
    };

      Note: 在枚举最后有一个公共静态方法collectDefaults(),这个方法返回一个整形,整形包含的是所有枚举项对应位bit为初始默认值(true:1;false:0),如果默认属性为true,则通过对1 << ordinal()的值和flags进行亦或来置位。

3.2、DeserializationConfig反序列化相关配置属性

     将Java对象序列化为Json字符串。Jackson在序列化Java对象的时候,对于有些不存在的属性处理,以及一些类型转换等,都可以通过配置来设置。

    /**
     * Enumeration that defines togglable features that guide the serialization feature.
     */
    public enum Feature implements MapperConfig.ConfigFeature {
        /*
         * /****************************************************** Introspection features
         * /******************************************************
         */

        /**
         * 该特性决定是否启动内部注解功能支持配置;如果允许,则使用AnnotationIntrospector扫描配置,否则,不考了注解配置。
         *
         * 默认启动该功能配置属性。
         * 
         * @since 1.2
         */
        USE_ANNOTATIONS(true),

        /**
         * 该特性决定是否使用“getter”方法来根据标准bean命名转换方式来自动检测。如果true,则所有公共的带有一个参数
         * 并且前缀为set的方法都将被当做setter方法。如果false,只会把显式注解的作为setter方法。
         *
         * 注意: 这个特性的优先级低于显式注解,并且只会在获取不到更细粒度配置的情况下。
         *
         */
        AUTO_DETECT_GETTERS(true),                        DETECT_IS_GETTERS(true),

        /**
         * 该特性决定是否使用creator方法来根据公共构造函数以及名字为“valueOf”的静态单参数方法自动检测。
         *
         * 注意:这个特性比每个类上注解的优先级要低。
         *
         */
        AUTO_DETECT_CREATORS(true),

        /**
         * 这个特性决定是否非静态field被当做属性。如果true,则所有公共成员field都被当做属性, 否则只有注解,才会被当做属性field。
         *
         */
        AUTO_DETECT_FIELDS(true),

        /**
         * 使用getter方法来作为setter方法(一般只处理集合和Maps,和其他没有setter的类型)。 该属性决定是否不需要setter方法,而只需要getter方法来修改属性。
         * 
         * 注意:该配置优先级低于setter。
         */
        USE_GETTERS_AS_SETTERS(true),

        /**
         * 该特性决定当访问属性时候,方法和field访问是否修改设置。 如果设置为true,则通过反射调用方法AccessibleObject#setAccessible 来允许访问不能访问的对象。
         * 
         */
        CAN_OVERRIDE_ACCESS_MODIFIERS(true),                GETTERS(false),

        /*
         * /****************************************************** /* Type conversion features
         * /******************************************************
         */

        /**
         * 该特性决定对于json浮点数,是否使用BigDecimal来序列化。如果不允许,则使用Double序列化。
         * 
         * 注意:该特性默认是关闭的,因为性能上来说,BigDecimal低于Double。
         *
         */
        USE_BIG_DECIMAL_FOR_FLOATS(false),

        /**
         * 该特性决定对于json整形(非浮点),是否使用BigInteger来序列化。如果不允许,则根据数值大小来确定 是使用Integer}, {@link Long} 或者
         * {@link java.math.BigInteger}
         *
         */
        USE_BIG_INTEGER_FOR_INTS(false),

        // [JACKSON-652]
        /**
         * 该特性决定JSON ARRAY是映射为Object[]还是List<Object>。如果开启,都为Object[],false时,则使用List。
         *
         * @since 1.9
         */
        USE_JAVA_ARRAY_FOR_JSON_ARRAY(false),

        /**
         * 该特性决定了使用枚举值的标准序列化机制:如果允许,则枚举假定使用Enum.toString()返回的值作为序列化结构;如果禁止, 则返回Enum.name()的值。
         *
         * 注意:默认使用的时Enum.name()的值作为枚举序列化结果。这个的设置和WRITE_ENUMS_USING_TO_STRING需要一致。
         *
         * For further details, check out [JACKSON-212]
         * 
         * @since 1.6
         */
        READ_ENUMS_USING_TO_STRING(false),

        /*
         * /****************************************************** Error handling features
         * /****************************************************** 错误处理特性
         */

        /**
         * 该特性决定了当遇到未知属性(没有映射到属性,没有任何setter或者任何可以处理它的handler),是否应该抛出一个
         * JsonMappingException异常。这个特性一般式所有其他处理方法对未知属性处理都无效后才被尝试,属性保留未处理状态。
         *
         * 默认情况下,该设置是被打开的。
         *
         * @since 1.2
         */
        FAIL_ON_UNKNOWN_PROPERTIES(true),

        /**
         * 该特性决定当遇到JSON null的对象是java 原始类型,则是否抛出异常。当false时,则使用0 for 'int', 0.0 for double 来设定原始对象初始值。
         *
         * 默认情况下,允许原始类型可以使用null。
         *
         * @since 1.7
         */
        FAIL_ON_NULL_FOR_PRIMITIVES(false),

        /**
         * 该特性决定JSON 整数是否是一个有效的值,当被用来反序列化Java枚举值。如果false,数字可以接受,并且映射为枚举的值ordinal();
         * 如果true,则数字不允许并且抛出JsonMappingException异常。后面一种行为原因是因为大部分情况下,枚举被反序列化为 JSON 字符串, 从而造成从整形到枚举的意外映射关系。
         *
         * Feature is disabled by default (to be consistent with behavior of Jackson 1.6), i.e. to allow use of JSON
         * integers for Java enums.
         * 
         * @since 1.7
         */
        FAIL_ON_NUMBERS_FOR_ENUMS(false),

        /**
         * 异常封装,不封装Error,catch异常之后,抛出IOException。默认封装异常。
         *
         * @since 1.7
         */
        WRAP_EXCEPTIONS(true),

        /*
         * /****************************************************** Structural conversion features
         * /****************************************************** 数据结构转换特性
         */

        /**
         * 该特性决定是否接受强制非数组(JSON)值到Java集合类型。如果允许,集合反序列化将尝试处理非数组值。
         *
         * Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java
         * collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle
         * non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for
         * compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out
         * JSON array in cases where there is just a single element in array.
         * 
         * @since 1.8
         */
        ACCEPT_SINGLE_VALUE_AS_ARRAY(false),

        /**
         * 该特征允许 unwrap根级别JSON 值,来匹配WRAP_ROOT_VALUE 序列化设置。
         *
         * @since 1.9
         */
        UNWRAP_ROOT_VALUE(false),

        /*
         * /****************************************************** Value conversion features
         * /****************************************************** 值转换特性
         */

        /**
         * 该特性可以允许JSON空字符串转换为POJO对象为null。如果禁用,则标准POJO只会从JSON null或者JSON对象转换过来;
         * 如果允许,则空JSON字符串可以等价于JSON null。
         * @since 1.8
         */
        ACCEPT_EMPTY_STRING_AS_NULL_OBJECT(false)

        ;

        final boolean _defaultState;

        private Feature(boolean defaultState) {
            _defaultState = defaultState;
        }

        @Override
        public boolean enabledByDefault() {
            return _defaultState;
        }

        @Override
        public int getMask() {
            return (1 << ordinal());
        }
    }

3.3、SerializationConfig序列化相关配置属性

/**
     * 定义序列化对象所需配置的一些枚举.
     */
    public enum Feature implements MapperConfig.ConfigFeature
    {
        /*
        /******************************************************
        /*  Introspection features
        /******************************************************
         */
        
        /**
         * 注解扫描配置
         */
        USE_ANNOTATIONS(true),

        /**
         * 获取getter方法,前缀为get
         */
        AUTO_DETECT_GETTERS(true),

        /**
         * 获取getter方法,前缀为is
         */
        AUTO_DETECT_IS_GETTERS(true),

        /**
         * 将对象所有的field作为json属性
         */
         AUTO_DETECT_FIELDS(true),

        /**
         * 该特性决定当访问属性时候,方法和field访问是否修改设置。 如果设置为true,
         * 则通过反射调用方法AccessibleObject#setAccessible 来允许访问不能访问的对象。
         */
        CAN_OVERRIDE_ACCESS_MODIFIERS(true),

        /**
         * 获取的getter方法需要setter方法,否则,所有发现的getter都可以作为getter方法。
         */
        REQUIRE_SETTERS_FOR_GETTERS(false),
        
        /*
        /******************************************************
        /* Generic output features
        /******************************************************
         */

        /**
         * 属性对应的值为null,是否需要写出来,write out。
         */
        @Deprecated
        WRITE_NULL_PROPERTIES(true),

        /**
         * 特征决定是使用运行时动态类型,还是声明的静态类型。
         * 也可以使用{@link JsonSerialize#typing} 注解属性
         */
        USE_STATIC_TYPING(false),

        /**
         * 该特性决定拥有view注解{@link org.codehaus.jackson.map.annotate.JsonView}的属性是否在JSON序列化视图中。如果true,则非注解视图,也包含;
         * 否则,它们将会被排除在外。
         *
         */
        DEFAULT_VIEW_INCLUSION(true),
        
        /**
         * 在JAVA中配置XML root{@XmlRootElement.name}注解,最后xml数据中会出现对应root根name.
         */
        WRAP_ROOT_VALUE(false),

        /**
         * 该特性对于最基础的生成器,使用默认pretty printer {@link org.codehaus.jackson.JsonGenerator#useDefaultPrettyPrinter}
         * 这只会对{@link org.codehaus.jackson.JsonGenerator}有影响.该属性值允许使用默认的实现。
         */
        INDENT_OUTPUT(false),

        /**
         * 是否对属性使用排序,默认排序按照字母顺序。
         */
        SORT_PROPERTIES_ALPHABETICALLY(false),
        
        /*
        /******************************************************
        /*  Error handling features
        /******************************************************
         */
        
        /**
         * 是否允许一个类型没有注解表明打算被序列化。默认true,抛出一个异常;否则序列化一个空对象,比如没有任何属性。
         *
         * Note that empty types that this feature has only effect on
         * those "empty" beans that do not have any recognized annotations
         * (like <code>@JsonSerialize</code>): ones that do have annotations
         * do not result in an exception being thrown.
         *
         * @since 1.4
         */
        FAIL_ON_EMPTY_BEANS(true),

        /**
         * 封装所有异常
         */
        WRAP_EXCEPTIONS(true),

        /*
        /******************************************************
        /* Output life cycle features
        /******************************************************
         */
        
         /**
          * 该特性决定序列化root级对象的实现closeable接口的close方法是否在序列化后被调用。
          * 
          * 注意:如果true,则完成序列化后就关闭;如果,你可以在处理最后,调用排序操作等,则为false。
          * 
          */
        CLOSE_CLOSEABLE(false),

        /**
         * 该特性决定是否在writeValue()方法之后就调用JsonGenerator.flush()方法。
         * 当我们需要先压缩,然后再flush,则可能需要false。
         * 
         */
        FLUSH_AFTER_WRITE_VALUE(true),
         
        /*
        /******************************************************
        /* Data type - specific serialization configuration
        /******************************************************
         */

        /**
         * 该特性决定是否将基于Date的值序列化为timestamp数字式的值,或者作为文本表示。
         * 如果文本表示,则实际格式化的时候会调用{@link #getDateFormat}方法。
         * 
         * 该特性可能会影响其他date相关类型的处理,虽然我们理想情况是只对date起作用。
         * 
         */
        WRITE_DATES_AS_TIMESTAMPS(true),

        /**
         * 是否将Map中得key为Date的值,也序列化为timestamps形式(否则,会被序列化为文本形式的值)。
         */
        WRITE_DATE_KEYS_AS_TIMESTAMPS(false),

        /**
         * 该特性决定怎样处理类型char[]序列化,是否序列化为一个显式的JSON数组,还是默认作为一个字符串。
         *
         */
        WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS(false),

        /**
         * 该特性决定对Enum 枚举值使用标准的序列化机制。如果true,则返回Enum.toString()值,否则为Enum.name()
         *
         */
        WRITE_ENUMS_USING_TO_STRING(false),

        /**
         * 这个特性决定Java枚举值是否序列化为数字(true)或者文本值(false).如果是值的话,则使用Enum.ordinal().
         * 该特性优先级高于上面的那个。
         * 
         * @since 1.9
         */
        WRITE_ENUMS_USING_INDEX(false),
        
        /**
         * 决定是否Map的带有null值的entry被序列化(true)
         *
         */
        WRITE_NULL_MAP_VALUES(true),

        /**
         * 决定容器空的属性(声明为Collection或者array的值)是否被序列化为空的JSON数组(true),否则强制输出。
         *
         * Note that this does not change behavior of {@link java.util.Map}s, or
         * "Collection-like" types.
         * 
         * @since 1.9
         */
        WRITE_EMPTY_JSON_ARRAYS(true)
        
            ;

        final boolean _defaultState;
        
        private Feature(boolean defaultState) {
            _defaultState = defaultState;
        }

        @Override
        public boolean enabledByDefault() { return _defaultState; }
    
        @Override
        public int getMask() { return (1 << ordinal()); }
    }

参考:

https://cloud.tencent.com/developer/article/1791434

https://ketao1989.github.io/posts/Jackson-Manual-and-Implementation-Analyzing.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值