精通ES+ES大数据查询常见的疑难杂症的解决与实现

11 篇文章 0 订阅
8 篇文章 0 订阅

目录

 什么是BoolQueryBuilder?

简单的复合查询

RestHighLevelClient中的matchQuery与matchPhraseQuery与termQuery的在实际使用中的不同

ES是否能在类型为text的字段的内部使用keyword,如果能这样做的含义是什么?

什么是ES多字段?

ES怎么实现聚合查询?

RestHighLevelClient的subAggregation是什么?干什么用的?

什么是RestHighLevelClient的滚动查询?如何实现?

ES构建简单搜索


 什么是BoolQueryBuilder?

BoolQueryBuilder是该客户端库中的一个查询构建器,用于构建布尔查询(bool query)。 布尔查询是一种复合查询,可以将多个查询条件组合在一起。

BoolQueryBuilder提供了几种方法来构建布尔查询:

1. must(QueryBuilders): 添加一个“必须匹配”(must)的查询条件。这意味着文档必须满足该查询条件才能被返回。 相当于sql中的and

2. mustNot(QueryBuilders): 添加一个“不能匹配”(must not)的查询条件。这意味着文档不能满足该查询条件才能被返回。相当于 is not 

3. should(QueryBuilders): 添加一个“应该匹配”(should)的查询条件。这意味着文档可以满足该查询条件,但不是必须的。 相当与or

4. filter(QueryBuilders): 添加一个“过滤”(filter)的查询条件。与must查询条件类似,但过滤查询不会计算相关性得分,通常用于对结果进行筛选。相当与select 的字段

简单的复合查询

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
    .must(QueryBuilders.termQuery("field1", "value1"))
    .mustNot(QueryBuilders.termQuery("field2", "value2"))
    .should(QueryBuilders.termQuery("field3", "value3"))
    .filter(QueryBuilders.termQuery("field4", "value4"));

// 将boolQuery用于搜索请求
SearchRequest searchRequest = new SearchRequest("indexName");
searchRequest.source(new SearchSourceBuilder().query(boolQuery));

RestHighLevelClient中的matchQuery与matchPhraseQuery与termQuery的在实际使用中的不同

1. matchQuery:  
The matchQuery is a full-text query that analyzes the provided text and generates a query that performs a full-text search on the analyzed text. It is useful when you want to perform a search based on relevance. The matchQuery analyzes the text and generates a set of terms that are used for matching.

翻译:

1.匹配查询:
matchQuery是一个全文查询,用于分析所提供的文本,并生成一个对所分析的文本执行全文搜索的查询。当您想要根据相关性执行搜索时,它非常有用。matchQuery分析文本并生成一组用于匹配的术语。

2. matchPhraseQuery: 
The matchPhraseQuery is similar to the matchQuery, but it analyzes the text as a phrase rather than individual terms. It is useful when you want to search for an exact phrase in the text. The matchPhraseQuery considers the order of the terms and their proximity to each other. 

2.匹配短语查询:
matchPhraseQuery类似于matchQuery,但它将文本作为短语而不是单个术语进行分析。当你想在文本中搜索一个确切的短语时,它很有用。matchPhraseQuery考虑术语的顺序及其彼此之间的接近程度。

termQuery: The termQuery is a query that searches for exact terms in a field. It does not analyze the text and is suitable for searching on keyword fields or fields that are not analyzed. The termQuery matches documents that contain an exact term.

术语查询:
termQuery是一个在字段中搜索精确术语的查询。它不分析文本,适用于搜索关键字字段或未分析的字段。termQuery匹配包含确切术语的文档。

总结:

matchQuery和matchPhraseQuery用于全文搜索并考虑相关性,而termQuery用于精确的术语匹配,不对文本执行分析。您可以根据特定的搜索要求选择适当的查询类型。

ok,

所以简单的来说,

如果搜随机的话请使用matchQuery来完成模糊匹配,

搜索模糊的关键词使用matchPhraseQuery

对指定字段进行精确的分词搜索,适用于关键词精确搜索。

ES是否能在类型为text的字段的内部使用keyword,如果能这样做的含义是什么?

在Elasticsearch中,你可以在类型为"text"的字段内部使用"keyword"。

这种使用方式被称为"multi-fields"(多字段),它允许你在同一个字段上定义多个不同类型的子字段。
 当你在类型为"text"的字段上同时定义一个"keyword"子字段时,它的含义是:
1. "text"类型子字段:该子字段会对文本进行分析,将其拆分为单个词项,并应用分词器和过滤器等分析器组件。这样可以支持全文搜索和相关性评分。
2. "keyword"类型子字段:该子字段会将整个文本作为一个项进行索引和搜索,不进行分词和分析。这样可以实现精确匹配、排序和聚合操作。
 这种使用方式的好处是,你可以在同一个字段上同时支持全文搜索和精确匹配操作,而无需额外定义多个字段。例如,你可以使用"text"类型子字段进行全文搜索,并使用"keyword"类型子字段进行精确匹配或排序。
 以下是一个示例,展示如何在Elasticsearch中定义一个包含"text"和"keyword"子字段的字段:
json
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
在上面的示例中,"title"字段被定义为"text"类型,并包含一个名为"keyword"的"keyword"类型子字段。这样,你可以同时在"title"字段上进行全文搜索和精确匹配操作。
 总而言之,你可以在类型为"text"的字段内部使用"keyword"子字段,以实现同时支持全文搜索和精确匹配的功能。

实战:

字段

				"agent_name": {
					"type": "text",
					"fields": {
						"keyword": {
							"ignore_above": 256,
							"type": "keyword"
						}
					}
				},

要求精准匹配到agent_name为河南神龙公司的所有信息。

        // 指定索引
        request.indices("smsp_collect_info_new");
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
        // 使用精准分词 查找
        searchSourceBuilder.query(QueryBuilders.termQuery("agent_name.keyword", "河南神龙公司"));
        //放入文档中
        request.source(searchSourceBuilder);
        //远程查询
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //获取结果
        SearchHit[] searchHits = response.getHits().getHits();
        for (SearchHit hit : searchHits) {
            System.out.println(hit.getSourceAsMap().get("agent_name"));
        }

这里的agent_name字段就是运用了多字段的思想/方式。

什么是ES多字段?

ES多字段(Multi-fields)是Elasticsearch中的一个概念,它允许在一个字段上定义多个不同类型的子字段。每个子字段可以根据不同的需求进行不同的处理和索引,以便在搜索和聚合时提供更灵活的功能。
 使用多字段的主要目的是在同一个字段上同时支持多种查询需求。例如,你可能想要在一个字段上进行全文搜索、精确匹配和排序等操作。通过使用多字段,你可以根据不同的查询需求定义不同类型的子字段,从而在每个子字段上使用适当的查询方式。
 多字段可以在映射(mapping)中定义。以下是一个示例,展示如何在Elasticsearch中定义一个包含多字段的字段:
json
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "english": {
            "type": "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}
在上面的示例中,"title"字段被定义为"text"类型,并包含两个子字段:"keyword"和"english"。其中,"keyword"子字段是"keyword"类型,用于精确匹配和排序,而"english"子字段是"text"类型,并使用英文分析器进行全文搜索。
 通过使用多字段,你可以根据不同的查询需求选择适当的子字段进行搜索、排序和聚合,从而提供更灵活和准确的搜索体验。
 总而言之,ES多字段是在一个字段上定义多个不同类型的子字段,用于在不同的查询需求下提供灵活的搜索和聚合功能。

ES怎么实现聚合查询?

主要是通过AggregationBuilders来进行聚合条件的构建,然后通过aggregation方法将AggregationBuilders拼接到SearchSourceBuilder 中实现的。如果有子聚合是通过subaggregation方法进行拼装的。

聚合查询的类型

- Range Aggregation:按指定的范围对字段进行聚合,比如按价格范围聚合商品。

- Date Histogram Aggregation:按指定的时间间隔对日期字段进行聚合,比如按月份聚合销售数据。

- Histogram Aggregation:按指定的数值间隔对数值字段进行聚合,比如按价格区间聚合商品。

- Terms Aggregation:对字段进行词条聚合,类似于SQL中的GROUP BY操作。

- Avg Aggregation:计算字段的平均值。

- Sum Aggregation:计算字段的总和。

- Max Aggregation:计算字段的最大值。

- Min Aggregation:计算字段的最小值。

- Cardinality Aggregation:计算字段的基数(不重复值的数量)。

查询+分组聚合实战

public void GroupSearch() throws IOException {
        SearchRequest request = new SearchRequest();

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        request.indices("smsp_collect_info_new");
        // 模糊分词查询
        searchSourceBuilder.query(QueryBuilders.matchQuery("province", "河南"));
        // 分组聚合查询
        searchSourceBuilder.aggregation(AggregationBuilders
                .terms("your_agg_name")
                .field("agent_name.keyword")
                .subAggregation(AggregationBuilders.count("count_number")
                        // 指定返回的桶的最大数量为10000
                        .field("company.keyword")).size(10000));

        //放入文档中
        request.source(searchSourceBuilder);
        //远程查询
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 获取所有的桶
        Terms termsAggregation = response.getAggregations().get("your_agg_name");
        for (Terms.Bucket bucket : termsAggregation.getBuckets()) {
            // 获得每次聚合的key的值
            System.out.println("当前的key为:"+bucket.getKeyAsString());
            ValueCount countAggregation =bucket.getAggregations().get("count_number");
            System.out.println("当前键下的聚合属性的总和,"+countAggregation.getValue());
        }
    }

RestHighLevelClient的subAggregation是什么?干什么用的?

RestHighLevelClient的subAggregation是用于在Elasticsearch中进行嵌套聚合操作的功能。在Elasticsearch中,聚合是一种用于对数据进行分组、过滤和计算的功能,可以根据不同的聚合类型对字段进行统计、计算平均值、求和等操作。

subAggregation允许在一个聚合操作的结果上进行进一步的嵌套聚合操作。

通过使用subAggregation,可以对聚合结果进行更细粒度的分析和处理。

例如,可以在terms聚合的结果上进行子聚合,对每个聚合桶进行更深入的统计。

以下是一个使用RestHighLevelClient进行subAggregation的示例代码:

RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("localhost", 9200, "http")));
 SearchRequest searchRequest = new SearchRequest("your_index_name"); // 替换为实际的索引名
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
 // 添加主聚合
TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("my_terms_agg")
        .field("your_field_name"); // 替换为实际的字段名
 // 添加子聚合
MaxAggregationBuilder subAggregationBuilder = AggregationBuilders.max("my_max_agg")
        .field("your_nested_field_name"); // 替换为实际的嵌套字段名
aggregationBuilder.subAggregation(subAggregationBuilder);
 searchSourceBuilder.aggregation(aggregationBuilder);
searchRequest.source(searchSourceBuilder);
 try {
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
     // 解析聚合结果
    Terms termsAggregation = searchResponse.getAggregations().get("my_terms_agg");
    for (Terms.Bucket bucket : termsAggregation.getBuckets()) {
        String key = bucket.getKeyAsString();
        Max maxAggregation = bucket.getAggregations().get("my_max_agg");
        double max = maxAggregation.getValue();
         // 处理聚合结果
        System.out.println("Bucket Key: " + key);
        System.out.println("Max Value: " + max);
    }
} catch (IOException e) {
    e.printStackTrace();
}
 client.close();

什么是RestHighLevelClient的滚动查询?如何实现?

RestHighLevelClient的滚动查询是一种用于在Elasticsearch中检索大量数据的机制。它允许您通过多个请求迭代地获取结果集,而不是一次性获取所有结果。滚动查询适用于需要处理大量数据的情况,例如导出数据、数据分析等。
 与其他查询方式相比,滚动查询具有以下优势:
1. 可以处理大量数据:滚动查询适用于处理超过单个请求的结果集大小限制的情况。它允许您按需获取数据,而不会因为数据量过大而导致请求失败或性能下降。
2. 节省内存:滚动查询通过每次返回一小批结果并在服务器端维护滚动上下文来减少内存消耗。这使得在处理大量数据时,不需要将所有结果加载到内存中,从而节省了内存资源。
3. 支持持续查询:滚动查询的结果可以被持续使用,即使在处理期间索引中的数据发生了变化。您可以使用滚动上下文来保持查询的一致性,而不受索引更新的影响。
4. 支持并发处理:通过使用滚动查询,您可以并发地处理多个滚动上下文,从而提高查询的吞吐量和效率。
 需要注意的是,滚动查询也有一些限制和注意事项:
1. 滚动查询会占用服务器资源:由于滚动上下文需要在服务器端维护,因此会占用一定的内存和计算资源。在设计滚动查询时,需要考虑服务器的可用资源以及查询的并发性能。
2. 滚动查询的结果可能不是实时的:如果在滚动查询期间索引中的数据发生了变化,滚动查询的结果可能不会反映最新的数据。因此,在滚动查询中处理数据时,需要注意数据的一致性和实时性。
 总之,RestHighLevelClient的滚动查询是一种强大的机制,可以高效地处理大量数据。它提供了灵活的分批获取结果的方式,适用于需要处理大数据量的场景。

实现:

public void ScrollSearch() throws IOException {
        SearchRequest request = new SearchRequest();
        //失效时间为10min
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(10));
        //封存快照
        request.scroll(scroll);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        request.indices("smsp_collect_info_new");
        searchSourceBuilder.query(QueryBuilders.matchQuery("province", "黑龙江"));
        // 每次查询2000个,是滚动查询条数的步长
        searchSourceBuilder.size(2000);
        //放入文档中
        request.source(searchSourceBuilder);
        //远程查询
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] searchHits = response.getHits().getHits();
        //处理第一次的响应结果
        for (SearchHit hit : searchHits) {
            String province = hit.getSourceAsMap().get("province").toString();
            System.out.println(province);
            // 处理每个查询结果
        }
        // 获取第一次的滚动id
        String scrollId = response.getScrollId();
        while (searchHits.length > 0) {
            // 每循环一次构建一个新的滚动请求。
            SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
            scrollRequest.scroll(TimeValue.timeValueMinutes(1));
            response = client.scroll(scrollRequest, RequestOptions.DEFAULT);
            // 获取新的滚动请求的滚动id
            scrollId = response.getScrollId();
            searchHits = response.getHits().getHits();
            // 处理下一批查询结果
            for (SearchHit hit : searchHits) {
                String province = hit.getSourceAsMap().get("province").toString();
                System.out.println(province);
                // 处理每个查询结果
            }
        }

    }

ES构建简单搜索

使用模糊匹配

public void simpleSearch() throws IOException {
        SearchRequest request = new SearchRequest();
        // 指定索引
        request.indices("smsp_collect_info_new");
        // 构建查询条件 查询某城市
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("city", "郑州"));
        //放入文档中
        request.source(searchSourceBuilder);
        //远程查询
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //获取结果
        SearchHit[] searchHits = response.getHits().getHits();
        for (SearchHit hit : searchHits) {
            System.out.println(hit.getSourceAsMap().get("city"));
        }
    }

 使用精准匹配

    // 单条件精准查询
    public void simpleSearchUseTerm() throws IOException {
        SearchRequest request = new SearchRequest();
        // 指定索引
        request.indices("smsp_collect_info_new");
        // 构建查询条件 查询某城市
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 使用精准分词 查找
        searchSourceBuilder.query(QueryBuilders.termQuery("agent_name.keyword", "河南神龙公司"));
        //放入文档中
        request.source(searchSourceBuilder);
        //远程查询
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //获取结果
        SearchHit[] searchHits = response.getHits().getHits();
        for (SearchHit hit : searchHits) {
            System.out.println(hit.getSourceAsMap().get("agent_name"));
        }
    }

如何在实现mysql中的in查询+条件查询

注意关键是QueryBuilders.termsQuery("字段名",list)

小知识:


sourceBuilder.fetchSource(false);

设置后就只返回元数据,加快速度,减少资源消耗

    // in查询子端口对应的id
    public List<String> getIdListBySubPortAndProvince(List<SPA> spaList) {
        if (CollectionUtils.isEmpty(spaList)) {
            return new ArrayList<>();
        }
        List<String> subPortList = spaList.stream().map(SPA::getSubPort).collect(Collectors.toList());
        List<String> list = new ArrayList<>();
        SearchRequest searchRequest = new SearchRequest("smsp_collect_info_new");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
        // 设置只返回元数据,加快速度
        sourceBuilder.fetchSource(false);
        //in查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.termsQuery("sub_port", subPortList))
                .must(QueryBuilders.termQuery("province.keyword", spaList.get(0).getProvince()));
        //
        sourceBuilder.query(boolQueryBuilder).size(10000);
        searchRequest.source(sourceBuilder);
        try {
            SearchResponse search = client.
                    search(searchRequest, RequestOptions.DEFAULT);
            log.info("查询结果为:{}", search.getHits().getTotalHits());
            for (SearchHit hit : search.getHits().getHits()) {
                list.add(hit.getId());
            }
        } catch (IOException e) {

            e.printStackTrace();
        }
        return list;
    }

对应的原始语句

POST /smsp_collect_info_new/_search
{
    "bool": {
        "must": [
            {
                "term": {
                    "sub_port": {
                        "value": [
                            "121212121212",
                            "12121212",
                            "1212122112122112",
                            "122121323243434"
                        ],
                        "boost": 1
                    }
                }
            },
            {
                "term": {
                    "province.keyword": {
                        "value": 312132,
                        "boost": 1
                    }
                }
            }
        ],
        "adjust_pure_negative": true,
        "boost": 1
    }
}

如何实现批量更新?

大致思路:

1 通过条件查询,获得所有的需要更新的数据的id(元数据中的id)

大数据量的情况使用滚动查询

2 将查询到的id作为更新的依据进行更新
 

实现:

1 滚动查询获取id的list

// 滚动查询主端口对应的id
    public List<String> SearchIdByMasterPortProvince(String masterPort, String province) {
        List<String> idList = new ArrayList<>();
        SearchRequest request = new SearchRequest();
        //失效时间为10min
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(10));
        //封存快照
        request.scroll(scroll);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        request.indices("smsp_collect_info_new");
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("master_port", masterPort))
                .must(QueryBuilders.termQuery("province.keyword", province));
        searchSourceBuilder.query(boolQueryBuilder);
        // 每次查询2000个,是滚动查询条数的步长
        searchSourceBuilder.size(2000);
        searchSourceBuilder.fetchSource(false);
        //放入文档中
        request.source(searchSourceBuilder);
        //远程查询
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        SearchHit[] searchHits = response.getHits().getHits();
        //处理第一次的响应结果
        for (SearchHit hit : searchHits) {
            idList.add(hit.getId());
        }
        // 获取第一次的滚动id
        String scrollId = response.getScrollId();
        while (searchHits.length > 0) {
            // 每循环一次构建一个新的滚动请求。
            SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
            scrollRequest.scroll(TimeValue.timeValueMinutes(1));
            try {
                response = client.scroll(scrollRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            // 获取新的滚动请求的滚动id
            scrollId = response.getScrollId();
            searchHits = response.getHits().getHits();
            // 处理下一批查询结果
            for (SearchHit hit : searchHits) {
                idList.add(hit.getId());
                // 处理每个查询结果
            }
        }
        return idList;
    }

2 更新,传入idlist

    // 批量更新ES
    public void updateEs(List<String> idList, String agentName) {
        HashMap<String, Object> hashMap = new HashMap<>();
        // 需要更新的字段
        hashMap.put("agent_name", agentName);
        // 创建批量请求
        BulkRequest bulkRequest = new BulkRequest();
        int count = 0;
        for (String s : idList) {
            UpdateRequest updateRequest = new UpdateRequest();
            updateRequest.index("smsp_collect_info_new")
                    .id(s)
                    .doc(hashMap);
            bulkRequest.add(updateRequest);
            count++;
            // 每3000个提交一次
            if (count % 3000 == 0) {
                try {
                    BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
                    log.info("ES更新成功,{}", bulk.getItems().length);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                count = 0;
                bulkRequest = new BulkRequest();
            }
        }
        if (count != 0) {
            try {
                BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
                log.info("ES更新成功,{}", bulk.getItems().length);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        log.info("ES全部更新成功!");
    }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在使用Docker时,可能会遇到一些疑难杂症。其中,一些常见的问题及解决办法如下: 1. 运行docker version时报错"Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?"这个错误通常是由于Docker守护进程未启动引起的。可以通过运行以下命令来启动守护进程:`sudo systemctl start docker`(适用于基于systemd的Linux发行版)。如果您不是使用systemd,请根据您的操作系统和版本来启动Docker守护进程。 2. 使用yum安装Docker时报错"Cannot retrieve metalink for repository: epel. Please verify its path and try again."这个错误通常是由于epel源(Extra Packages for Enterprise Linux)未正确安装或配置引起的。您可以尝试以下解决办法: - 首先,确保您的系统与互联网连接正常。 - 检查您的操作系统和版本,并根据官方文档正确安装epel源。 - 如果您已经安装了epel源,但仍然遇到这个错误,请尝试更新epel源并再次运行安装命令。 这些是一些常见的Docker疑难杂症及其解决办法。当然,Docker的使用过程中可能还会遇到其他问题,您可以参考官方文档、社区论坛或搜索引擎来寻找更多解决办法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [docker 疑难杂症](https://blog.csdn.net/weixin_33805992/article/details/92266045)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [docker常见疑难杂症](https://blog.csdn.net/weixin_45776707/article/details/103142818)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桂亭亭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

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

抵扣说明:

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

余额充值