Elasticsearch:从搜索中获取选定的字段 fields

Elastic 专栏收录该内容
494 篇文章 87 订阅

在实际的搜索返回数据中,我们经常会用选择地返回所需要的字段或部分的 source。这在某些情况下非常有用,因为对于大规模的数据来说,返回的数据大下直接影响网路带宽的使用以及内存的使用。默认情况下,搜索响应中的每个匹配都包含文档 _source,这是在为文档建立索引时提供的整个 JSON 对象。 要检索搜索响应中的特定字段,可以使用 fields 参数:

POST my-index-000001/_search
{
  "query": {
    "match": {
      "message": "foo"
    }
  },
  "fields": ["user.id", "@timestamp"],
  "_source": false
}

fields 参数同时查询文档的 _source 和索引 mapping,以加载和返回值。因为它利用了 mapping,所以与直接引用 _source 相比,字段具有一些优点:它接受 multi-fields 和字段别名,并且还以一致的方式设置诸如日期之类的字段值的格式。

文档的 _source 存储在 Lucene 中的单个字段中。因此,即使仅请求少量字段,也必须加载并解析整个 _source 对象。为避免此限制,你可以尝试另一种加载字段的方法:

  • 使用 docvalue_fields 参数获取选定字段的值。当返回相当少量的支持 doc 值的字段(例如关键字和日期)时,这是一个不错的选择。
  • 使用 stored_fields 参数获取特定存储字段(使用 store 映射选项的字段)的值。

你还可以使用 script_field 参数通过脚本来转换响应中的字段值。

你可以在以下各节中找到有关这些方法的更多详细信息:

Fields

fields 参数允许检索搜索响应中的文档字段列表。 它同时查阅文档 _source 和索引 mapping,以符合其映射类型的标准化方式返回每个值。 默认情况下,日期字段是根据其 mapping 中的日期格式参数设置格式的。 你还可以使用 fields 参数来检索运行时字段值。我们使用如下的文档作为例子:

PUT developers/_doc/1
{
  "name": {
    "firstname": "san",
    "secondname": "zhang"
  },
  "age": 20,
  "city": "Beijing",
  "skills": ["Java", "C++"],
  "DOB": "1988-06-04"
}

PUT developers/_doc/2
{
  "name": {
    "firstname": "si",
    "secondname": "li"
  },
  "age": 30,
  "city": "Shanghai",
  "skills": ["Ruby", "C++"],
  "DOB": "1999-07-08"
}

在上面,我们创建了一个叫做 developers 的索引。其中的 DOB 字段指的是 date of birth。上面的 developer 的 mapping 是:

GET developers/_mapping
{
  "developers" : {
    "mappings" : {
      "properties" : {
        "DOB" : {
          "type" : "date"
        },
        "age" : {
          "type" : "long"
        },
        "city" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "name" : {
          "properties" : {
            "firstname" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "secondname" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "nianling" : {
          "type" : "alias",
          "path" : "age"
        },
        "skills" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

以下搜索请求使用 fields 参数来检索 city 字段,以 skil*开头的所有字段, name.firstname 以及 DOB 字段的值:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "fields": [
    "name.firstname",
    "ski*",
    "nianling",
    "city",
    {
      "field": "DOB",
      "format": "epoch_millis"
    }
  ]
}

在上面,我也使用了一个 alias 字段 nianling。上面的显示结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "name" : {
            "firstname" : "san",
            "secondname" : "zhang"
          },
          "age" : 20,
          "city" : "Beijing",
          "skills" : [
            "Java",
            "C++"
          ],
          "DOB" : "1988-06-04"
        },
        "fields" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name.firstname" : [
            "san"
          ],
          "city" : [
            "Beijing"
          ],
          "DOB" : [
            "612921600000"
          ],
          "nianling" : [
            20
          ],
          "skills.keyword" : [
            "Java",
            "C++"
          ]
        }
      }
    ]
  }
}

如果我们不想要 _source,我们也可以直接使用如下的查询:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "fields": [
    "name.firstname",
    "ski*",
    "nianling",
    "city",
    {
      "field": "DOB",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}

上面显示的结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "fields" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name.firstname" : [
            "san"
          ],
          "city" : [
            "Beijing"
          ],
          "DOB" : [
            "612921600000"
          ],
          "nianling" : [
            20
          ],
          "skills.keyword" : [
            "Java",
            "C++"
          ]
        }
      }
    ]
  }
}

在上面我们看到 DOB 是以我们想要的格式进行显示的。我们也可以使用 ski* 来显示 multi-fields 字段 skills 以及 skill.keyword。fields 不允许返回整个对象。它只能返回 leaf field。

在这里特别指出的是,我们可以直接可以通过  source filtering 的方法来返回 _source 中的部分字段:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "_source": ["city", "age", "name"]
}

上面搜索的返回结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "city" : "Beijing",
          "name" : {
            "secondname" : "zhang",
            "firstname" : "san"
          },
          "age" : 20
        }
      }
    ]
  }
}

fields 参数处理字段类型,例如字段 aliasesconstant_keyword,其值并不总是出现在 _source 中。 还应其他映射选项也被考虑,包括 ignore_aboveignore_malformednull_value

注意:即使 _source 中只有一个值,fields 响应也总是为每个字段返回一个值数组。 这是因为 Elasticsearch 没有专用的数组类型,并且任何字段都可以包含多个值。 fields 参数也不能保证以特定顺序返回数组值。 有关更多背景信息,请参见映射文档中的 array

目前在 Kibana 中的 Discover 中,基本都是使用 fiields 而不 source 来进行显示的:

处理 nested 字段

nested 字段的字段响应与常规对象字段的字段响应略有不同。 常规对象字段内的 leaf value 作为扁平列表返回时,nested 字段内的值被分组以维护原始 nested 数组内每个对象的独立性。 对于 nested 字段数组内的每个条目,除非父 nested 对象内还有其他 nested 字段,否则值将再次作为一个扁平列表返回,在这种情况下,将对较深的 nested 字段再次重复相同的过程。

给定以下 mapping,其中 user 是一个 nested 字段,在索引以下文档并检索到 user 字段下的所有字段之后:

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "group" : { "type" : "keyword" },
      "user": {
        "type": "nested",
        "properties": {
          "first" : { "type" : "keyword" },
          "last" : { "type" : "keyword" }
        }
      }
    }
  }
}

PUT my-index-000001/_doc/1?refresh=true
{
  "group" : "fans",
  "user" : [
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

POST my-index-000001/_search
{
  "fields": ["*"],
  "_source": false
}

响应会将 first 和 last 分组,而不是将它们作为扁平列表返回。

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "user" : [
            {
              "last" : [
                "Smith"
              ],
              "first" : [
                "John"
              ]
            },
            {
              "last" : [
                "White"
              ],
              "first" : [
                "Alice"
              ]
            }
          ],
          "group" : [
            "fans"
          ]
        }
      }
    ]
  }
}

无论用于搜索它们的模式如何,nested 字段都将按其 nested 路径进行分组。 例如,在上面的示例中仅查询 user.first 字段:

POST my-index-000001/_search
{
  "fields": ["user.first"],
  "_source": false
}

将仅返回用户的 first 字段,但仍保持 nested 用户数组的结构:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "user" : [
            {
              "first" : [
                "John"
              ]
            },
            {
              "first" : [
                "Alice"
              ]
            }
          ]
        }
      }
    ]
  }
}

但是,当字段模式直接定位嵌套的 user 字段时,由于该模式与任何 leaf field 都不匹配,因此不会返回任何值。就像上面所说的那样,它不能返回任何 Object。

检索未映射的字段

默认情况下,fields 参数仅返回映射字段的值。 但是,Elasticsearch 允许将未映射的字段存储在 _source 中,例如,通过将 “动态字段映射” 设置为 false 或使用具有 enable: false的对象字段,从而禁用对其内容的解析和索引编制。

可以使用字段部分中的 include_unmapped 选项从 _source 检索此类对象中的字段:

PUT my-index-000002
{
  "mappings": {
    "enabled": false 
  }
}

在上面,我们对索引 my-index-000002 禁止所有的 mapping。它也意味着我们不能对它做任何的搜索:

GET my-index-000002/_search
{
  "query": {
    "match": {
      "user_id": "kimchy"
    }
  }
}

上面的搜索将不会返回任何的数值。

PUT my-index-000002/_doc/1?refresh=true
{
  "user_id": "kimchy",
  "session_data": {
     "object": {
       "some_field": "some_value"
     }
   }
}

POST my-index-000002/_search
{
  "fields": [
    "user_id",
    {
      "field": "session_data.object.*",
      "include_unmapped" : true 
    }
  ],
  "_source": false
}

即使未映射字段,响应也将在 session_data.object.* 路径下包含字段结果。由于未映射但 user_id 字段模式的 include_unmapped 标志未设置为 true,因此响应中将不包含 user_id。上面的搜索结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000002",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "session_data.object.some_field" : [
            "some_value"
          ]
        }
      }
    ]
  }
}

Doc value 字段

你可以使用 docvalue_fields 参数返回搜索响应中一个或多个字段的 doc value。

Doc value 存储与 _source 相同的值,但采用基于磁盘的基于列的结构,该结构针对排序和聚合进行了优化。 由于每个字段都是单独存储的,因此 Elasticsearch 仅读取请求的字段值,并且可以避免加载整个文档 _source。

默认情况下,将为支持的字段存储文档值。 但是,text 或 text_annotated 字段不支持  doc  value。

PUT developers/_doc/1
{
  "name": {
    "firstname": "san",
    "secondname": "zhang"
  },
  "age": 20,
  "city": "Beijing",
  "skills": ["Java", "C++"],
  "DOB": "1988-06-04"
}

PUT developers/_doc/2
{
  "name": {
    "firstname": "si",
    "secondname": "li"
  },
  "age": 30,
  "city": "Shanghai",
  "skills": ["Ruby", "C++"],
  "DOB": "1999-07-08"
}

以下搜索请求使用 docvalue_fields 参数检索 age 字段,以 ski*开头的所有字段以及 DOB 字段的 doc value:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "docvalue_fields": [
    "age",
    "ski*.keyword",
    {
      "field": "date",
      "format": "epoch_millis"
    }
  ]
}

提示:你不能使用 docvalue_fields 参数来检索 nested 对象的 doc value。 如果指定 nested 对象,则搜索将为该字段返回一个空数组([])。 要访问 nested 字段,请使用inner_hits 参数的 docvalue_fields 属性。

上面的搜索结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "name" : {
            "firstname" : "san",
            "secondname" : "zhang"
          },
          "age" : 20,
          "city" : "Beijing",
          "skills" : [
            "Java",
            "C++"
          ],
          "DOB" : "1988-06-04"
        },
        "fields" : {
          "age" : [
            20
          ],
          "skills.keyword" : [
            "C++",
            "Java"
          ]
        }
      }
    ]
  }
}

Stored fields

也可以使用 store 映射选项来存储单个字段的值。 你可以使用 stored_fields 参数将这些存储的值包括在搜索响应中。

警告:stored_fields 参数用于显式标记为存储在映射中的字段,该字段默认情况下处于关闭状态,通常不建议使用。 而是使用源过滤来选择要返回的原始源文档的子集。

允许有选择地为 search hit 表示的每个文档加载特定的 store 字段。

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "store": true 
      },
      "date": {
        "type": "date",
        "store": true 
      },
      "content": {
        "type": "text"
      },
      "city": {
        "type": "keyword"
      }
    }
  }
}

PUT my_index/_doc/1
{
  "title": "Some short title",
  "date": "2015-01-01",
  "content": "A very long content field...",
  "city": "Beijing"
}

我们可以通过如下的方式来进行搜索:

GET my_index/_search
{
  "stored_fields": [
    "title",
    "date"
  ],
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

上面的搜索结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "fields" : {
          "date" : [
            "2015-01-01T00:00:00.000Z"
          ],
          "title" : [
            "Some short title"
          ]
        }
      }
    ]
  }
}

在上面,我们可以使用 * 来返回所有的 stored fields:

GET my_index/_search
{
  "stored_fields": "*",
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

空数组将导致每次匹配仅返回 _id 和 _type,例如:

GET my_index/_search
{
  "stored_fields": [],
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

上面的搜索将返回:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821
      }
    ]
  }
}

如果未设定为 store 请求的字段(将存储映射设置为false),则将忽略它们。

从文档本身获取的 store 字段值始终以数组形式返回。 相反,诸如 _routing 之类的元数据字段从不作为数组返回。

另外,只能通过 stored_fields 选项返回 leaf field。 如果指定了对象字段,它将被忽略。

注意:就其本身而言,stored_fields 不能用于加载 nested 对象中的字段 - 如果字段在其路径中包含 nested 对象,则不会为该存储字段返回任何数据。 要访问 nested 字段,必须在 inner_hits 块内使用 stored_fields。

禁止 stored fields

要完全禁用 store 字段(和元数据字段),请使用:_none_ :

GET my_index/_search
{
  "stored_fields": "_none_",
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

Soure filtering

你可以使用 _source 参数选择返回源的哪些字段。 这称为源过滤。

以下搜索 API 请求将 _source 请求主体参数设置为 false。 该文档来源不包含在响应中。

GET developers/_search
{
  "_source": false,
  "query": {
    "match": {
      "city": "Beijing"
    }
  }
}

要仅返回 source 字段的子集,请在 _source参 数中指定通配符(*)模式。 以下搜索 API 请求仅返回 ski 为开头的所有字段以及 name.fir 为开头的所有字段:

GET developers/_search
{
  "_source": ["ski*", "name.fir*"],
  "query": {
    "match": {
      "city": "Beijing"
    }
  }
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name" : {
            "firstname" : "san"
          }
        }
      }
    ]
  }
}

为了更好地控制,你可以在 _source 参数中指定一个includes 和  excludes 模式的数组的对象。

如果指定了 includes 属性,则仅返回与其模式之一匹配的源字段。 你可以使用 excludes 属性从此子集中排除字段。

如果未指定 include 属性,则返回整个文档源,不包括与 excludes 属性中与模式匹配的任何字段。

以下搜索 API 请求仅返回 sk* 和 name 字段及其属性的源,不包括 name.secondname 字段。

GET developers/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
    "includes": [ "sk*", "name" ],
    "excludes": [ "name.secondname"]
  }
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name" : {
            "firstname" : "san"
          }
        }
      },
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "skills" : [
            "Ruby",
            "C++"
          ],
          "name" : {
            "firstname" : "si"
          }
        }
      }
    ]
  }
}

Scripted fields

你可以使用 script_fields 参数为每个 hit 检索进行脚本运算(基于不同的字段)。 例如:

GET developers/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "2_times_age": {
      "script": {
        "source": """
          doc['age'].value * 2
        """
      }
    },
    "3_times_age": {
      "script": {
        "source": """
          doc['age'].value * 3
        """
      }
    }
  }
}

上面将返回:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "2_times_age" : [
            40
          ],
          "3_times_age" : [
            60
          ]
        }
      },
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "fields" : {
          "2_times_age" : [
            60
          ],
          "3_times_age" : [
            90
          ]
        }
      }
    ]
  }
}

Scripted fields 可以在未 store 的字段上工作(在上述情况下为 age),并允许返回要返回的自定义值(脚本的计算值)。

脚本字段还可以使用 params['_ source'] 访问实际的 _source 文档并提取要从中返回的特定元素。 这是一个例子:

GET developers/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "my_city": {
      "script": {
        "source": """
          "I am living in " + params["_source"]["city"]
        """
      }
    }
  }
}
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "my_city" : [
            "I am living in Beijing"
          ]
        }
      },
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "fields" : {
          "my_city" : [
            "I am living in Shanghai"
          ]
        }
      }
    ]
  }
}

请注意此处的 _source 关键字,以浏览类似 json 的模型。

重要的是要了解 doc['my_field'].value 和 params['_ source'] ['my_field'] 之间的区别。 第一个使用 doc 关键字,将导致将该字段的术语加载到内存中(缓存),这将导致执行速度更快,但会占用更多内存。 此外,doc [...]表示法仅允许使用简单值字段(你无法从中返回 json 对象),并且仅对未分析或基于单个术语的字段有意义。 但是,从文档访问值的方式来说,仍然建议使用doc(即使有可能),因为 _source 每次使用时都必须加载和解析。 使用 _source 非常慢。

  • 1
    点赞
  • 8
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

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

抵扣说明:

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

余额充值