第二章 ES数据类型介绍

一、文档字段介绍

1、核心数据类型

#字符串类型:string,字符串类还可被分为textkeyword类型,如果我们让es自动映射数据,那么es会把字符串定义为text,并且还加了一个keyword类型字段。

text文本数据类型,用于索引全文值的字段。使用文本数据类型的字段,它们会被分词,在索引之前将字符串转换为单个术语的列表(倒排索引),分词过程允许ES搜索每个全文字段中的单个单词。什么情况适合使用text,只要不具备唯一性的字符串一般都可以使用text

keyword关键字数据类型,用于索引结构化内容的字段使用keyword类型的字段,其不会被分析,给什么值就原封不动地按照这个值索引,所以关键字字段只能按其确切值进行搜索。什么情况下使用keyword,具有唯一性的字符串,例如:电子邮件地址、MAC地址、身份证号、状态代码...等等。

# 数字型数据类型:long、integer、short、byte、double、float

# 日期类型:date

# 布尔类型:boolean

2、复杂数据类型

# 数组:无需专门的数据类型

# 对象数据类型:单独的JSON对象

# 嵌套数据类型:nested,关于JSON对象的数组

3、地理数据类型

# 地理点数据类型

# 地理形状数据类型

4、专门数据类型:

# IPv4数据类型

# 单词计数数据类型token_count

我们结合前面的映射来看看:

创建一个新的索引:

PUT /open-soft

显式映射:

PUT /open-soft/_mapping

{

  "properties": {

    "corp": {

      "type": "text"

    },

    "lang": {

      "type": "text"

    },

    "name": {

      "type": "text"

    }

  }

}

索引或者说入库一个文档,注意这个文档的字段,比我们显示映射的字段要多个star字段:

PUT /open-soft/_doc/1

{

  "name": "Apache Hadoop",

  "lang": "Java",

  "corp": "Apache",

  "stars": 200

}

通过GET /open-soft/_mapping,我们可以看到es自动帮我们新增了stars这个字段。

修改映射,增加一个新的字段:

PUT /open-soft/_mapping

{

  "properties": {

    "year": {

      "type": "integer"

    }

  }

}

5、数组

不需要特殊配置,一个字段如果被配置为基本数据类型,就是天生支持数组类型的。任何字段都可以有0个或多个值,但是在一个数组中数据类型必须一样。比如:

PUT /open-soft/_doc/2

{

  "name": [

    "Apache Activemq",

    "Activemq Artemis"

  ],

  "lang": "Java",

  "corp": "Apache",

  "stars": [

    500,

    200

  ]

}

是没问题的,但是如果: 

PUT /open-soft/_doc/3

{

"name": ["Apache Kafka"],

"lang": "Java",

"corp": "Apache",

"stars":[500,"kafka"]

}

则会出错。

 

6、对象

JSON文档是有层次结构的,一个文档可能包含其他文档,如果一个文档包含其他文档,那么该文档值是对象类型,其数据类型是对象。当然ElasticSearch中是没有所谓对象类型的,比如:

PUT /open-soft/_doc/object

{

  "name": [

    "Apache ShardingSphere"

  ],

  "lang": "Java",

  "corp": "JingDong",

  "stars": 400,

  "address": {

    "city": "BeiJing",

    "country": "亦庄"

  }

}

查询结构映射:

get /open-soft/_mapping

返回结果:

{

  "open-soft" : {

    "mappings" : {

      "properties" : {

        "address" : {

          "properties" : {

            "city" : {

              "type" : "text",

              "fields" : {

                "keyword" : {

                  "type" : "keyword",

                  "ignore_above" : 256

                }

              }

            },

            "country" : {

              "type" : "text",

              "fields" : {

                "keyword" : {

                  "type" : "keyword",

                  "ignore_above" : 256

                }

              }

            }

          }

        },

        "corp" : {

          "type" : "text"

        },

        "lang" : {

          "type" : "text"

        },

        "name" : {

          "type" : "text"

        },

        "stars" : {

          "type" : "long"

        },

        "year" : {

          "type" : "integer"

        }

      }

    }

  }

}

对象类型可以在定义索引的映射关系时进行指定。

7、多数据类型

如果说数组允许你使用同一个设置索引多项数据,那么多数据类型允许使用不同的设置,对同一项数据索引多次。带来的好处就是可以同一文本有多种不同的索引方式,比如一个字符串类型的字段,可以使用text类型做全文检索,使用keyword类型做聚合和排序。我们可以看到es的动态映射生成的字段类型里,往往字符串类型都使用了多数据类型。当然,我们一样也可以自己定义:

PUT /open-soft/_mapping

{

  "properties": {

    "name": {

      "type": "text",

      "fields": {

        "raw": {

          "type": "keyword"

        },

        "length": {

          "type": "token_count",

          "analyzer": "standard"

        }

      }

    }

  }

}

在上面的代码里,我们使用"fields"就把name字段扩充为多字段类型,为name新增了两个子字段raw和length,raw设置类型为keyword,length设置类型为 token_count,告诉es这个字段在保存还需要做词频统计。通过fields字段设置的子字段raw和length,在我们添加文档时,并不需要单独设置值,他们name共享相同的值,只是es会以不同的方式处理字段值。

同样在检索文档的时候,它们也不会显示在结果中,所以它们一般都是在检索中以查询条件的形式出现,以减少检索时的性能开销。

二、字段参数

在上面的代码里出现了analyzer这个词,这是什么?这个叫字段参数,和type一样,可以用来对字段进行配置。常用的字段参数和作用如下:analyzer指定分词器。elasticsearch是一款支持全文检索的分布式存储系统,对于text类型的字段,首先会使用分词器进行分词,然后将分词后的词根一个一个存储在倒排索引中,后续查询主要是针对词根的搜索。

1、analyzer

该参数可以在每个查询、每个字段、每个索引中使用,其优先级如下(越靠前越优先):

1)字段上定义的分词器

2)索引配置中定义的分词器

3)默认分词器(standard)

2、normalizer

规范化,主要针对keyword类型,在索引该字段或查询字段之前,可以先对原始数据进行一些简单的处理,然后再将处理后的结果当成一个词根存入倒排索引中,默认为null,比如:

PUT index

{

  "settings": {

    "analysis": {

      "normalizer": {

        "my_normalizer": { // 1

        "type": "custom",

        "char_filter": [],

        "filter": ["lowercase", "asciifolding"] // 2

        }

        

      }

      

    }

  },

  "mappings": {

    "_doc": {

      "properties": {

        "foo": {

          "type": "keyword",

          "normalizer": "my_normalizer" // 3

          }

      }

    }

  }

}

代码 1:首先在settings中的analysis属性中定义normalizer。

代码 2:设置标准化过滤器,示例中的处理器为小写、asciifolding。

代码 3:在定义映射时,如果字段类型为keyword,可以使用normalizer引用定义好的normalizer

3、boost

权重值,可以提升在查询时的权重,对查询相关性有直接的影响,其默认值为1.0。其影响范围为词根查询(team query),对前缀、范围查询。5.0 版本后已废止。

4、coerce

数据不总是我们想要的,由于在转换JSON body为真正JSON的时候,整型数字5有可能会被写成字符串"5"或者浮点数5.0,这个参数可以将数值不合法的部分去除。默认为 true。

例如:将字符串会被强制转换为整数、浮点数被强制转换为整数。

例如存在如下字段类型:

"number_one": {

"type": "integer"

}

声明number_one字段的类型为数字类型,那是否允许接收“6”字符串形式的数据呢?因为在JSON中,“6”用来赋给int类型的字段,也是能接受的,默认coerce为true,表示允许这种赋值,但如果coerce 设置为false,此时es只能接受不带双引号的数字,如果在coerce=false时,将“6”赋值给number_one时会抛出类型不匹配异常。

5、copy_to

copy_to参数允许您创建自定义的_all字段。换句话说,多个字段的值可以复制到一个字段中。

例如,first_name和last_name字段可以复制到full_name字段如下:

PUT my_index

{

  "mappings": {

    "_doc": {

      "properties": {

        "first_name": {

          "type": "text",

          "copy_to": "full_name"

        },

        "last_name": {

          "type": "text",

          "copy_to": "full_name"

        },

        "full_name": {

          "type": "text"

        }

      }

    }

  }

}

表示字段 full_name 的值来自first_name + last_name。

关于copy_to重点说明:

1)字段的复制是原始值。

2)同一个字段可以复制到多个字段,写法如下:“copy_to”: [ “field_1”,“field_2” ]

6、doc_values

Doc values的存在是因为倒排索引只对某些操作是高效的。倒排索引的优势在于查找包含某个项的文档,而对于从另外一个方向的相反操作并不高效,即:确定哪些项是否存在单个文档里,聚合需要这种次级的访问模式。

对于以下倒排索引:

 

如果我们想要获得所有包含brown的文档的词的完整列表,倒排索引是根据词项来排序的,所以我们首先在词项列表中找到brown,然后扫描所有列,找到包含brown的文档。我们可以快速看到Doc_1和Doc_2包含brown这个token。然后,对于聚合部分,我们需要找到Doc_1和Doc_2里所有唯一的词项。用倒排索引做这件事情代价很高:我们会迭代索引里的每个词项并收集Doc_1和Doc_2列里面token。这很慢而且难以扩展:随着词项和文档的数量增加,执行时间也会增加。

Doc values通过转置两者间的关系来解决这个问题。倒排索引将词项映射到包含它们的文档,doc values 将文档映射到它们包含的词项:

Doc Terms

--------------------------------------------------------------

Doc_1 | brown, dog, fox, jumped, lazy, over, quick, the

Doc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summer

Doc_3 | dog, dogs, fox, jumped, over, quick, the

--------------------------------------------------------------

当数据被转置之后,想要收集到Doc_1和Doc_2的唯一token会非常容易。获得每个文档行,获取所有的词项,然后求两个集合的并集。

doc_values缺省是true,即是开启的,并且只适用于非text类型的字段。

7、dynamic

是否允许动态的隐式增加字段。在执行index api或更新文档API时,对于_source字段中包含一些原先未定义的字段采取的措施,根据dynamic的取值,会进行不同的操作:

true默认值,表示新的字段会加入到类型映射中。

false新的字段会被忽略,即不会存入_souce字段中,即不会存储新字段,也无法通过新字段进行查询。

strict会显示抛出异常,需要新使用 put mapping api 先显示增加字段映射。

8、enabled

是否建立索引,默认情况下为true,es会尝试为你索引所有的字段,但有时候某些类型的字段,无需建立索引,只是用来存储数据即可。也就是说,ELasticseaech 默认会索引所有的字段,enabled设为false的字段,elasicsearch会跳过字段内容,该字段只能从_source中获取,但是不可搜。只有映射类型(type)和 object类型的字段可以设置enabled属性。

9、eager_global_ordinals

表示是否提前加载全局顺序号。Global ordinals是一个建立在doc values和fielddata基础上的数据结构, 它为每一个精确词按照字母顺序维护递增的编号。

每一个精确词都有一个独一无二的编号并且精确词A小于精确词B的编号,Global ordinals只支持keyword和text型字段,在keyword字段中,默认是启用的而在text型字段中只有fielddata和相关属性开启的状态下才是可用的。

10、fielddata

为了解决排序与聚合,elasticsearch提供了doc_values属性来支持列式存储,但doc_values不支持text字段类型。因为text字段是需要先分析(分词),会影响doc_values列式存储的性能。

es为了支持text字段高效排序与聚合,引入了一种新的数据结构(fielddata),使用内存进行存储。默认构建时机为第一次聚合查询、排序操作时构建,主要存储倒排索引中的词根与文档的映射关系,聚合,排序操作在内存中执行。因此fielddata需要消耗大量的JVM堆内存。一旦fielddata加载到内存后,它将永久 存在。

通常情况下,加载fielddata是一个昂贵的操作,故默认情况下,text字段的字段默认是不开启fielddata机制。在使用fielddata之前请慎重考虑为什么要开启fielddata。

11、format

在 JSON 文档中,日期表示为字符串。Elasticsearch使用一组预先配置的格式来识别和解析这些字符串,并将其解析为long类型的数值(毫秒),支持自定义格式,也有内置格式。比如:

PUT my_index

{

"mappings": {

"_doc": {

"properties": {

"date": {

"type":

"date",

"format": "yyyy-MM-dd HH:mm:ss"

}

}

}

}

}

elasticsearch 为我们内置了大量的格式,如下:

epoch_millis

时间戳,单位,毫秒,范围受限于 Java Long.MIN_VALUE 和 Long.MAX_VALUE。

epoch_second

时间戳,单位,秒,范围受限于 Java 的限制 Long.MIN_VALUE 并 Long.

MAX_VALUE 除以 1000(一秒中的毫秒数)。

date_optional_time 或者 strict_date_optional_time

日期必填,时间可选,其支持的格式如下:date-opt-time = date-element ['T' [time-element] [offset]]

date-element = std-date-element | ord-date-element | week-date-element

std-date-element = yyyy ['-' MM ['-' dd]]

ord-date-element = yyyy ['-' DDD]

week-date-element = xxxx '-W' ww ['-' e]

time-element = HH [minute-element] | [fraction]

minute-element = ':' mm [second-element] | [fraction]

second-element = ':' ss [fraction]

比如"yyyy-MM-dd"、"yyyyMMdd"、"yyyyMMddHHmmss"、

"yyyy-MM-ddTHH:mm:ss"、"yyyy-MM-ddTHH:mm:ss.SSS"、

"yyyy-MM-ddTHH:mm:ss.SSSZ"格式,不支持常用的"yyyy-MM-dd HH:mm:ss"等格式。

注意,"T"和"Z"是固定的字符。

tips:如果看到“strict_”前缀的日期格式要求,表示 date_optional_time 的

严格级别,这个严格指的是年份、月份、天必须分别以 4 位、2 位、2 位表示,

不足两位的话第一位需用 0 补齐。

basic_date

其格式表达式为 :yyyyMMdd

basic_date_time

其格式表达式为:yyyyMMdd’T’HHmmss.SSSZ

basic_date_time_no_millis

其格式表达式为:yyyyMMdd’T’HHmmssZ

basic_ordinal_date

4 位数的年 + 3 位(day of year),其格式字符串为 yyyyDDD

basic_ordinal_date_time

其格式字符串为 yyyyDDD’T’HHmmss.SSSZ

basic_ordinal_date_time_no_millis

其格式字符串为 yyyyDDD’T’HHmmssZ

basic_time

其格式字符串为 HHmmss.SSSZ

basic_time_no_millis

其格式字符串为 HHmmssZ

basic_t_time

其格式字符串为’T’HHmmss.SSSZ

basic_t_time_no_millis

其格式字符串为’T’HHmmssZ

basic_week_date其格式字符串为 xxxx’W’wwe,4 为年 ,然后用’W’, 2 位 week of year

(所在年里周序号) 1 位 day of week。

basic_week_date_time

其格式字符串为 xxxx’W’wwe’T’HH:mm:ss.SSSZ.

basic_week_date_time_no_millis

其格式字符串为 xxxx’W’wwe’T’HH:mm:ssZ.

date

其格式字符串为 yyyy-MM-dd

date_hour

其格式字符串为 yyyy-MM-dd’T’HH

date_hour_minute

其格式字符串为 yyyy-MM-dd’T’HH:mm

date_hour_minute_second

其格式字符串为 yyyy-MM-dd’T’HH:mm:ss

date_hour_minute_second_fraction

其格式字符串为 yyyy-MM-dd’T’HH:mm:ss.SSS

date_hour_minute_second_millis

其格式字符串为 yyyy-MM-dd’T’HH:mm:ss.SSS

date_time

其格式字符串为 yyyy-MM-dd’T’HH:mm:ss.SSS

date_time_no_millis

其格式字符串为 yyyy-MM-dd’T’HH:mm:ss

hour

其格式字符串为 HH

hour_minute

其格式字符串为 HH:mm

hour_minute_second

其格式字符串为 HH:mm:ss

hour_minute_second_fraction

其格式字符串为 HH:mm:ss.SSS

hour_minute_second_millis

其格式字符串为 HH:mm:ss.SSS

ordinal_date

其格式字符串为 yyyy-DDD,其中 DDD 为 day of year。

ordinal_date_time

其格式字符串为 yyyy-DDD‘T’HH:mm:ss.SSSZZ,其中 DDD 为 day of year。ordinal_date_time_no_millis

其格式字符串为 yyyy-DDD‘T’HH:mm:ssZZ

time

其格式字符串为 HH:mm:ss.SSSZZ

time_no_millis

其格式字符串为 HH:mm:ssZZ

t_time

其格式字符串为’T’HH:mm:ss.SSSZZ

t_time_no_millis

其格式字符串为’T’HH:mm:ssZZ

week_date

其格式字符串为 xxxx-'W’ww-e,4 位年份,ww 表示 week of year,e 表示 day

of week。

week_date_time

其格式字符串为 xxxx-'W’ww-e’T’HH:mm:ss.SSSZZ

week_date_time_no_millis

其格式字符串为 xxxx-'W’ww-e’T’HH:mm:ssZZ

weekyear

其格式字符串为 xxxx

weekyear_week

其格式字符串为 xxxx-'W’ww,其中 ww 为 week of year。

weekyear_week_day

其格式字符串为 xxxx-'W’ww-e,其中 ww 为 week of year,e 为 day of week。

year

其格式字符串为 yyyy

year_month

其格式字符串为 yyyy-MM

year_month_day

其格式字符串为 yyyy-MM-dd

12、ignore_above

ignore_above用于指定字段索引和存储的长度最大值,超过最大值的会被忽略。

13、ignore_malformed

ignore_malformed可以忽略不规则数据,对于login字段,有人可能填写的是date类型,也有人填写的是邮件格式。给一个字段索引不合适的数据类型发生异常,导致整个文档索引失败。如果ignore_malformed参数设为true,异常会被忽略,出异常的字段不会被索引,其它字段正常索引。

14index

index属性指定字段是否索引,不索引也就不可搜索,取值可以为true或者false,缺省为true。

15、index_options

index_options控制索引时存储哪些信息到倒排索引中,用于搜索和突出显示目的。

doc:只存储文档编号

freqs:存储文档编号和词项频率。

positions:文档编号、词项频率、词项的位置被存储

offsets:文档编号、词项频率、词项的位置、词项开始和结束的字符位置都被存储。 

16、fields

fields可以让同一文本有多种不同的索引方式,比如一个String类型的字段,可以使用text类型做全文检索,使用keyword类型做聚合和排序。

17、norms

norms参数用于标准化文档,以便查询时计算文档的相关性。norms虽然对评分有用,但是会消耗较多的磁盘空间,如果不需要对某个字段进行评分,最好不要开启norms。

18、null_value

一般来说值为null的字段不索引也不可以搜索,null_value参数可以让值为null的字段显式的可索引、可搜索。

PUT my_index

{

"mappings": {

"my_type": {

"properties": {

"status_code": {

"type":

"keyword",

"null_value": "NULL"

}

}

}

}}

PUT my_index/my_type/1

{

"status_code": null

}

PUT my_index/my_type/2

{

"status_code": []

}

GET my_index/_search

{

"query": {

"term": {

"status_code": "NULL"

}

}

}

文档1可以被搜索到,因为status_code的值为null,文档2不可以被搜索到,因为status_code为空数组,但是不是null。

19、position_increment_gap

文本数组元素之间位置信息添加的额外值。

举例,一个字段的值为数组类型:"names": [ "John Abraham", "Lincoln Smith"]

为了区别第一个字段和第二个字段,Abraham 和 Lincoln 在索引中有一个间距,默认是 100。例子如下,这是查询”Abraham Lincoln”是查不到的:

PUT my_index/groups/1

{

"names": [ "John Abraham", "Lincoln Smith"]

}

GET my_index/groups/_search

{

"query": {

"match_phrase": {"names": {

"query": "Abraham Lincoln"

}

}

}

}

指定间距大于 10 0 可以查询到:

GET my_index/groups/_search

{

"query": {

"match_phrase": {

"names": {

"query": "Abraham Lincoln",

"slop": 101

}

}

}

}

想要调整这个值,在mapping中通过position_increment_gap参数指定间距即可。 

20、properties

Object或者nested类型,下面还有嵌套类型,可以通过properties参数指定。

比如:

PUT my_index

{

"mappings": {

"my_type": {

"properties": {

"manager": {

"properties": {

"age": { "type": "integer" },

"name": { "type": "text" }

}

},"employees": {

"type": "nested",

"properties": {

"age": { "type": "integer" },

"name": { "type": "text" }

}

}

}

}

}

}

对应的文档结构:

PUT my_index/my_type/1

{

"region": "US",

"manager": {

"name": "Alice White",

"age": 30

},

"employees": [

{

"name": "John Smith",

"age": 34

},

{

"name": "Peter Brown",

"age": 26

}

]

}

21、search_analyzer

通常,在索引时和搜索时应用相同的分析器,以确保查询中的术语与反向索引中的术语具有相同的格式,如果想要在搜索时使用与存储时不同的分词器,则使用search_analyzer属性指定,通常用于ES实现即时搜索(edge_ngram)。

22、similarity

指定相似度算法,其可选值:BM25

当前版本的默认值,使用BM25算法。

classic:使用TF/IDF算法,曾经是es,lucene的默认相似度算法。

boolean:一个简单的布尔相似度,当不需要全文排序时使用,并且分数应该只基于查询条件是否匹配。布尔相似度为术语提供了一个与它们的查询boost相等的分数。

23、store

默认情况下,字段值被索引以使其可搜索,但它们不存储。这意味着可以查询字段,但无法检索原始字段值。通常这并不重要。字段值已经是_source字段的一部分,该字段默认存储。如果您只想检索单个字段或几个字段的值,而不是整个_source,那么这可以通过字段过滤上下文source filting context来实现。

在某些情况下,存储字段是有意义的。例如,如果您有一个包含标题、日期和非常大的内容字段的文档,您可能只想检索标题和日期,而不需要从大型_source 字段中提取这些字段,可以将标题和日期字段的store定义为true。

24、term_vector

Term vectors包含分析过程产生的索引词信息,包括:

索引词列表

每个索引词的位置(或顺序)

索引词在原始字符串中的原始位置中的开始和结束位置的偏移量。

term vectors会被存储,索引它可以作为一个特使的文档返回。

term_vector可取值: 

  • no不存储 term_vector 信息,默认值。
  • yes 只存储字段中的值。
  • with_positions 存储字段中的值与位置信息。
  • with_offsets 存储字段中的值、偏移量
  • with_positions_offsets 存储字段中的值、位置、偏移量信息。

三、元字段meta-fields

一个文档根据我们定义的业务字段保存有数据之外,它还包含了元数据字段(meta-fields)。元字段不需要用户定义,在任一文档中都存在,有点类似于数据库的表结构数据。在名称上有个显著的特征,都是以下划线“_”开头。我们可以看看:get /open-soft/_doc/1 大体分为五种类型:

身份(标识)元数据、索引元数据、文档元数据、路由元数据以及其他类型的元数据,当然不是每个文档这些元字段都有的。

1、身份(标识)元数据

_index:文档所属索引,自动被索引,可被查询,聚合,排序使用,或者脚本里访问。

_type:文档所属类型,自动被索引,可被查询,聚合,排序使用,或者脚本里访问。

_id:文档的唯一标识,建索引时候传入 ,不被索引,可通过_uid 被查询,脚本里使用,不能参与聚合或排序。

_uid:由_type_id字段组成,自动被索引 ,可被查询,聚合,排序使用,或者脚本里访问,6.0.0 版本后已废止。

2、索引元数据

_all:自动组合所有的字段值,以空格分割,可以指定分器词索引,但是整个值不被存储,所以此字段仅仅能被搜索,不能获取到具体的值。6.0.0 版本后已废止。

_field_names:索引了每个字段的名字,可以包含null值,可以通过exists查询或missing查询方法来校验特定的字段。

3、文档元数据

_source一个doc的原生的json数据,不会被索引,用于获取提取字段值,启动此字段,索引体积会变大,如果既想使用此字段又想兼顾索引体积,可以开启索引压缩。_source是可以被禁用的,不过禁用之后部分功能不再支持,这些功能包括:部分update api、运行时高亮搜索结果索引重建、修改mapping以及分词、索引升级debug查询或者聚合语句索引自动修复。

_size整个_source字段的字节数大小,需要单独安装一个mapper-size插件才能展示。 

4、路由元数据

_routing一个doc可以被路由到指定的shard上。

5、其他

_meta一般用来存储应用相关的元信息。

例如:

PUT /open-soft/_mapping

{

  "_meta": {

    "class": "cn.chj.User",

    "version": {

      "min": "1.0",

      "max": "1.3"

    }

  }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值