ElasticSearch保姆级教程[1]——SpringBoot 项目中使用 ES

该教程主要分为 三篇文章。此为第一篇——主要介绍 ElasticSearch、Kibana 的安装 以及如何 通过 Http 和Java API的方式使用 ElasticSearch。学完本文,你就可以 在SpringBoot 项目中使用 ES了。第二篇超链接第三篇超链接

一、实现ElasticSearch 单机运行

1.1 安装运行ElasticSearch

首先,我们可以在 ElasticSearch官网下载 7.10.2版本,这里给出window版下载地址:

https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.10.2-windows-x86_64.zip

该版本使用的是java jdk1.8,下载到本地后,直接解压到你决定的路径下(例如:E:\development\elasticsearch\)

运行其 bin文件夹下的 elasticsearch.bat,es 开始运行,并默认使用端口 9200向外界提供服务。

浏览器访问 localhost:9200 可查看 es的相关信息,以校验运行是否成功

在这里插入图片描述

1.2 安装运行Kibana

同时,如果你想更加方便的使用 ElasticSearch,可以下载 Kibana。Kibana类似于 Naciate等数据库管理软件,提供可视化的管理界面,7帮助你操作 ElasticSearch。因为我们下载的ES版本是 7.10.2 ,所以 Kibana 也选择使用 7.10.2 版本。这里给出下载地址:https://artifacts.elastic.co/downloads/kibana/kibana-7.10.2-windows-x86_64.zip 建议使用迅雷下载,浏览器下载较慢。

  • 下载解压到本地后,修改其 config 文件夹下的配置文件 kibana.yml,保证以下内容生效:
server.port: 5601
elasticsearch.hosts: ["http://localhost:9200"]
i18n.locale: "zh-CN"	#设置默认语言为中文
  • 修改配置之后,双击bin 文件夹下的 kibana.bat,即可启动 Kibana 。在地址:http://localhost:5601/ 即可访问当Kibana页面。

在这里插入图片描述

二、ElasticSearch基础操作

2.1 HTTP方式操作 ES

使用Postman发送http请求到 ES 来实现对 ES的操作

2.1.1 操作索引
  • 索引-创建:

    postman发送 Put 请求

    localhost:9200/shopping
    // 其中 localhost:9200 -> es所在机器 ip
    // shopping -> 希望新建索引名称
    

    在这里插入图片描述

    请求返回:

    {
        "acknowledged": true,			//表明 创建成功
        "shards_acknowledged": true,	//表明 分片分配成功
        "index": "shopping"				//索引名称
    }
    
  • 索引-查看索引信息

    Postman发送 Get 请求

    localhost:9200/shopping
    

    请求返回:

    {
        "shopping": {
            "aliases": {},
            "mappings": {},
            "settings": {
                "index": {
                    "routing": {
                        "allocation": {
                            "include": {
                                "_tier_preference": "data_content"
                            }
                        }
                    },
                    "number_of_shards": "1",
                    "provided_name": "shopping",
                    "creation_date": "1651737136026",
                    "number_of_replicas": "1",
                    "uuid": "qEAGozjASpqbBl9p1gFmbw",
                    "version": {
                        "created": "7100299"
                    }
                }
            }
        }
    }
    
  • 索引-查看ES中所有索引

    Postman发送 Get 请求

    localhost:9200/_cat/indices?v
    // 加上 ?v 是展示索引 详细 信息
    

    请求返回:

    health status index                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size
    green  open   .apm-custom-link                PtMtWtlDR-SO8dlk_T9wTA   1   0          0            0       208b           208b
    green  open   .kibana_task_manager_1          QPx1WNAqSd-aevNgUj-2wg   1   0          5           48     48.5kb         48.5kb
    green  open   .apm-agent-configuration        Sa1lFlJDRkOWjT9X-QUjWA   1   0          0            0       208b           208b
    green  open   .async-search                   cPqqbj48QzS9W5Yplk9mBQ   1   0          1            0     29.3kb         29.3kb
    green  open   kibana_sample_data_flights      2DaAg0rtSyqHjn-kog0HfQ   1   0      13059            0      5.6mb          5.6mb
    green  open   .kibana-event-log-7.10.2-000001 mGeBIM_LRiOQ0AsamKwMGw   1   0          1            0      5.6kb          5.6kb
    green  open   .kibana_1                       rsmoI7DyRgW6Npm0ZCLOmA   1   0         95           10      2.1mb          2.1mb
    yellow open   shopping                        qEAGozjASpqbBl9p1gFmbw   1   1          0            0       208b           208b
    
  • 索引-删除

    Postman发送 DELETE 请求

    localhost:9200/shopping
    
2.1.2 操作文档
  • 文档-创建

    索引创建完毕后,就可以创建文档了。ES中的文档类似于MySQL中的表数据(表中的1条记录)。

    Postman发送 Post 请求

    localhost:9200/shopping/_doc			//添加 数据,id 由 ES 自动生成
    localhost:9200/shopping/_doc/0001		//添加 0001 后,将使用 0001 作为数据的唯一id
    localhost:9200/shopping/_create/0001		//同上效果
    

    请求体内容:

    {
        "title":"华为Mate40",
        "category":"华为",
        "images":"https://",
        "price":5800
    }
    

    在这里插入图片描述

    请求返回:

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "32mMk4ABYnhfGB3XaX2d",			//数据的唯一标识(自动生成/自定义),类似主键
        "_version": 1,
        "result": "created",
        "_shards": {
            "total": 2,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 0,
        "_primary_term": 1
    }
    
  • 文档-查询(主键查询)

    localhost:9200/shopping/_doc/0001		//使用Get方式请求
    

    请求返回:

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "0001",
        "_version": 1,
        "_seq_no": 1,
        "_primary_term": 1,
        "found": true,	
        "_source": {
            "title": "华为Mate40",
            "category": "华为",
            "images": "https://",
            "price": 5800
        }
    }
    
  • 文档-全查询

    localhost:9200/shopping/_search		//使用Get方式请求
    

    请求将返回全部文档

  • 文档-完全覆盖修改

    localhost:9200/shopping/_doc/0001		//使用Put方式请求
    

    请求体 json格式(同添加时一样,放在请求体中):

    {
        "title": "华为Mate40",
        "category": "华为",
        "images": "https://xiugai",
        "price": 5800
    }
    

    请求返回:

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "0001",
        "_version": 2,
        "result": "updated",		//修改成功
        "_shards": {
            "total": 2,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 2,
        "_primary_term": 1
    }
    
  • 文档-局部修改

    localhost:9200/shopping/_update/0001		//Post方式请求
    

    请求体:

    {
        "doc" : {
            "price" : 5900		//填写需要修改的字段
        }
    }
    
  • 文档-删除

    localhost:9200/shopping/_doc/0001 		//DELETE方式请求	
    

    请求返回:

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "0001",
        "_version": 4,
        "result": "deleted",
        "_shards": {
            "total": 2,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 4,
        "_primary_term": 1
    }
    
2.1.3 文档-高级查询
  • 条件查询

    该请求将查询 shopping索引中 字段 category 字段值为 华为 的文档

    localhost:9200/shopping/_search?q=category:华为		//Get请求
    

    这种方式 是将请求参数放在 URL 路径上,该方式虽然便捷,但是可能存在乱码或者请求头过长等问题。所以还可以将请求参数放在请求体中, 如下:

    localhost:9200/shopping/_search		//请求路径(后续几个查询,都使用此路径)
    //请求体:
    {
        "query" : {
            "match" : {
                "category" : "华为"
            }
        }
    }
    

    全量查询,可以将 match更改为match_all 。当然全量查询数据量过大,所以一般分页查询。

  • 分页查询

    {
        "query" : {
            "match" : {
                "category" : "华为"
            }
        },
        "from" : 0,				//分页起始位置
        "size" : 100			//每页最多100条记录
    }
    
  • 查询文档指定字段

    {
        "query" : {
            "match" : {
                "category" : "华为"
            }
        },
        "_source" : ["title"]	//只查看文档的title字段
    }
    
  • 指定字段排序

    {
        "query" : {
            "match" : {
                "category" : "华为"
            }
        },
        "sort" : {
        	"price" : {			//根据price字段进行排序操作
    			"order" : "asc"    	
        	}
        }
    }
    
  • 多条件查询

    • must:多条件必须同时成立
    • should:满足一个条件即可
    • filter:范围查询
    {
    	"query" : {
    		"bool" : {
    			"must" : [		// 此位置可以使用must、should
                    {		
                        "match" : {
                            "category" : "华为"
                        }
                    },
                    {
                        "match" : {
                            "price" : 5900
                        }   
                    }
                ],
                "filter" : {	
                    "range" : {
                        "price" : {		//筛选价格字段值大于5000的
                            "gt" : 5000
                        }
                    }
                }
    		}
    		
    	}
    }
    
  • 全文检索

    如果match字段中category为 华 ,其实也是可以查询到的。这是因为当保存文档数据时,es会将文字进行分词进行拆解操作,将拆解后的数据进行倒排索引中。这样即使使用文字的一部分,也可以查询到文档。这种检索方式,即全文检索。

    当然如果你不希望使用这种全文检索,可以将 match字段 更改为 match_phrase

  • 高亮显示

    这里的高亮显示,其实是 ES 会对结果中 指定字段进行 特殊高亮处理。前端可以拿到这个 高亮字段 进行 字段的高亮显示。比如这里我们使用 match 全文检索华 ,那么文档 category字段中 符合检索条件的 内容会被高亮显示。

    {
    	"query" : {
    		"match" : {
                "category" : "华"
            }
    	},
        "highlight" : {
            "fields" : {
                "category" : {}
            }
        }
    }
    

    不太理解的话,可以看看百度。搜索 华 。检索到的很多内容标题 中 符合检索条件——华 的部分高亮显示了。

    在这里插入图片描述

  • 聚合查询

    • term:分组
    • avg:平均值
    {
    	"aggs" : {                  //聚合操作
            "price_group" : {       //统计结果的名称(自定义)
                "terms" : {         //操作类别:term、avg
                    "field" : "price"      
                }
            }
        }
    }
    
2.1.4 文档-映射关系

先创建一个新的索引来演示:

localhost:9200/user 		//Put方式请求

然后设置该索引文档映射关系:

localhost:9200/user/_mapping	//Put方式请求

请求体:

{
	"properties" : {
		"name" : {
            "type" : "text",        //文本内容
            "index" : true			//能够被索引,即支持该字段的查询
        },
        "sex" : {
            "type" : "keyword",     //不能分词,查询时必须完全匹配
            "index" : true
        },
        "tel" : {
            "type" : "keyword",
            "index" : false
        }
	}
}

这个的作用就是对索引中 所有文档的字段进行设置。比如字段类型(不同检索规则)、是否能够被索引。

2.2 Java API 方式操作 ES(SpringBoot集成ES)

这里给大家提供一个 SpringBoot 的Maven项目 帮助大家直接上手。将项目下拉到本地后,可以先看一下项目的README.md。

github地址:https://github.com/kongxiaoran/elasticsearch-learning

对下面对 ES 操作的 代码 在项目的 ElasticSearchLearningApplicationTests 类中

2.2.1 操作索引
  • 索引-创建

    //创建请求
    CreateIndexRequest request = new CreateIndexRequest("users");
    //执行请求,使用默认的请求配置
    CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
    System.out.println("创建索引 "+ ((response.isAcknowledged()==true)?"成功了":"失败了"));
    
  • 索引-查询

    //创建请求
    GetIndexRequest request = new GetIndexRequest("users");
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println("索引"+(exists==true?"":"不")+"存在");
    if(exists){
        GetIndexResponse getIndexResponse =
        client.indices().get(request,RequestOptions.DEFAULT);
        System.out.println(getIndexResponse.getAliases());
        System.out.println(getIndexResponse.getMappings());
        System.out.println(getIndexResponse.getSettings());
    }
    
  • 索引-删除

    DeleteIndexRequest request = new DeleteIndexRequest("test1");
    AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println(delete.isAcknowledged());//删除成功返回true,失败返回false
    
2.2.2 操作文档
  • 文档-添加

    //创建添加数据
    User user = new User("张三",23,"男");
    //声明要保存到那个索引库
    IndexRequest request = new IndexRequest("users");
    request.id("001").timeout("1s");
    
    //给请求放入数据.向ES中插入数据,必须将数据转换成 JSON 格式.将数据转化成JSON字符串的方法,可自选
    request.source(JSON.toJSONString(user), XContentType.JSON);
    //request.source(new ObjectMapper().writeValueAsString(user),XContentType.JSON);
    
    //执行请求
    IndexResponse resp = client.index(request, RequestOptions.DEFAULT);
    System.out.println(resp);//和我们使用命令添加时显示的差不多
    
  • 文档-更新

    //声明修改数据
    User user = new User();
    user.setAge(32);		//修改之前为 23
    //声明索引库
    UpdateRequest request = new UpdateRequest("users","001");
    request.id("001").timeout("1s");//设置修改的文档id和请求超时时间
    request.doc(JSON.toJSONString(user),XContentType.JSON);		
    // request.doc(XContentType.JSON,"age",32); 适合局部修改
    
    //执行修改  修改的时候,如果对象中某个字段没有给值,那么也会修改成默认值
    UpdateResponse update = client.update(request,RequestOptions.DEFAULT);
    System.out.println(update);
    System.out.println(update.status());
    
  • 文档-查看文档是否存在

    GetRequest request = new GetRequest("users","001");
    boolean exists = client.exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);//存在返回true,不存在返回false
    
  • 文档-根据id获取文档

    GetRequest request = new GetRequest("users","001");
    GetResponse resp = client.get(request, RequestOptions.DEFAULT);
    System.out.println(resp);
    System.out.println(resp.getSourceAsString());//获取文档内容的字符串,没有数据为null
    
  • 文档-删除

    DeleteRequest request = new DeleteRequest("users","001");
    DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(delete);
    System.out.println(delete.status());
    
  • 文档-批量新增

    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    
    List<User> list = new ArrayList<>();
    list.add(new User("chen1",20,"男"));
    list.add(new User("chen2",21,"男"));
    list.add(new User("chen3",22,"男"));
    list.add(new User("chen4",23,"男"));
    list.add(new User("chen5",24,"男"));
    list.add(new User("chen6",25,"男"));
    list.add(new User("chen7",26,"男"));
    list.add(new User("chen8",27,"男"));
    
    //注意:id要是重复,则会覆盖掉
    for (int i = 0; i < list.size(); i++) {
        bulkRequest.add(new IndexRequest("users")
        	.id(""+(i+1))
            .source(JSON.toJSONString(list.get(i)), XContentType.JSON));
    }
    //执行
    BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println(bulk);
    System.out.println(bulk.status());
    
  • 文档-批量删除

    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    
    for (int i = 0; i < 8; i++) {
        bulkRequest.add(new DeleteRequest("users",""+(i+1)));
    }
    //执行
    BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println(bulk);
    System.out.println(bulk.status());
    
2.2.3 文档-高级查询
  • 文档-条件查询

    //声明请求
    SearchRequest request = new SearchRequest("users");
    //创建查询构造器对象
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //精准查询条件构造器,还可以封装很多的构造器,都可以使用QueryBuilders这个类构建
    //QueryBuilders里面封装了我们使用的所有查询筛选命令
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "chen1");
    //把查询条件构造器放入到查询构造器中
    builder.query(termQueryBuilder);
    
    //把条件构造器放入到请求中
    request.source(builder);
    //执行查询
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //这个查询就和我们使用命令返回的结果是一致的
    System.out.println(JSON.toJSONString(search.getHits().getHits()));
    System.out.println("==============================================");
    for (SearchHit hit : search.getHits().getHits()) {
        //遍历获取到的hits,让每一个hit封装为map形式
        System.out.println(hit.getSourceAsMap());
    }
    
  • 分页查询

    //声明请求
    SearchRequest request = new SearchRequest("users");
    //创建查询构造器对象
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "chen1");
    
    // * 把条件构造器放入到请求中。这里初始查询构造器时就设置了分页的size与from
    request.source(new SearchSourceBuilder().from(0).size(100).query(termQueryBuilder));
    
    //执行查询
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //这个查询就和我们使用命令返回的结果是一致的
    System.out.println(JSON.toJSONString(search.getHits().getHits()));
    System.out.println("==============================================");
    for (SearchHit hit : search.getHits().getHits()) {
        //遍历获取到的hits,让每一个hit封装为map形式
        System.out.println(hit.getSourceAsMap());
    }
    
  • 查询文档-按指定字段值排序

    剩余代码同上,只将上方第7行代码更改为:

    //把条件构造器放入到请求中。这里初始查询构造器时就设置了分页的size与from以及排序规则
    request.source(new SearchSourceBuilder().from(0).size(100).sort("age",SortOrder.DESC).query(termQueryBuilder));
    
  • 查询文档-过滤文档中一些不需要的字段,只显示需要的字段

    //把条件构造器放入到请求中。这里初始查询构造器时就设置了分页的size与from以及排序规则
    request.source(new SearchSourceBuilder().from(0).size(100).sort("age", SortOrder.DESC)
    	.fetchSource(new String[]{"name"},new String[]{}).query(termQueryBuilder));
    
  • 组合查询

    //初始化条件构造器:设置组合查询
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // 必须 age = 24 && sex != "男"
    // boolQueryBuilder.must(QueryBuilders.matchQuery("age",24));
    // boolQueryBuilder.mustNot(QueryBuilders.matchQuery("sex","男"));
    
    // 必须 age = 24 || sex = "男"
    boolQueryBuilder.should(QueryBuilders.matchQuery("age",24));
    boolQueryBuilder.should(QueryBuilders.matchQuery("sex","男"));
    
    //把条件构造器放入到请求中。这里初始查询构造器时就设置了分页的size与from以及排序规则,然后查询构造器再执行
    request.source(new SearchSourceBuilder().from(0).size(100).sort("age", SortOrder.DESC)
    	.fetchSource(new String[]{"name"},new String[]{}).query(boolQueryBuilder));
    
  • 范围查询

    SearchRequest request = new SearchRequest();
    request.indices("users");
    
    SearchSourceBuilder builder = new SearchSourceBuilder();
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
    rangeQueryBuilder.gte(25).lt(30);            // 25<= x <30
    builder.query(rangeQueryBuilder);
    request.source(builder);
    
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
        System.out.println(hit.getSourceAsString());
    }
    
  • 范围查询 && 模糊查询

    前面我们基本都是使用 单种 查询。这里演示一下,同时对不同字段执行不同类型查询的 操作

    SearchRequest request = new SearchRequest().indices("users");
    
    SearchSourceBuilder builder = new SearchSourceBuilder();
    // name 字段进行模糊查询 寻找 符合 chen ,可容忍误差在 1个字符内
    FuzzyQueryBuilder fuzzinessBuild = QueryBuilders.fuzzyQuery("name", "chen").fuzziness(Fuzziness.ONE);
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").gte(22).lt(30);
    
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(fuzzinessBuild).must(rangeQueryBuilder);
    builder.query(boolQueryBuilder);
    
    request.source(builder);
    
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
    	System.out.println(hit.getSourceAsString());
    }
    
  • 高亮查询

    SearchSourceBuilder builder = new SearchSourceBuilder();
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "chen");
    builder.query(termQueryBuilder).highlighter();      //将 termQuery 查询的内容 高亮显示
    
    SearchResponse response = client.search(new SearchRequest().indices("users").
    source(builder),RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
    	System.out.println(hit.getSourceAsString());
    }
    
  • 聚合查询

    • 最大值查询

      SearchSourceBuilder builder = new SearchSourceBuilder();
      builder.aggregation(AggregationBuilders.max("maxAge").field("age"));
      
      SearchResponse response = client.search(new SearchRequest().indices("users").source(builder)
      	,RequestOptions.DEFAULT);
      SearchHits hits = response.getHits();
      //查询到的最大值的情况
      System.out.println(JSON.toJSONString(response.getAggregations()));
      for (SearchHit hit : hits) {
      	System.out.println(hit.getSourceAsString());
      }
      
    • 分组查询

      SearchSourceBuilder builder = new SearchSourceBuilder();
      builder.aggregation(AggregationBuilders.terms("age").field("age"));
      
      SearchResponse response = client.search(new SearchRequest().indices("users").source(builder)
      	,RequestOptions.DEFAULT);
      SearchHits hits = response.getHits();
      //查询到的分组情况
      System.out.println(JSON.toJSONString(response.getAggregations()));
      for (SearchHit hit : hits) {
      	System.out.println(hit.getSourceAsString());
      }
      
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot项目使用Elasticsearch,需要在pom.xml文件添加相关依赖。以下是一个使用Spring BootElasticsearch的简单示例: 1.添加依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> ``` 2.配置Elasticsearch属性 在application.properties文件添加Elasticsearch的配置属性,例如: ```properties spring.data.elasticsearch.cluster-name=my-application spring.data.elasticsearch.cluster-nodes=localhost:9300 ``` 3.定义实体类 在Spring Boot应用程序,可以使用Spring Data Elasticsearch来定义Elasticsearch实体类。例如: ```java @Document(indexName="blog",type="article") public class Article { @Id private String id; private String title; private String content; // getters and setters } ``` 4.定义Elasticsearch存储库 定义一个Elasticsearch存储库接口,例如: ```java public interface ArticleRepository extends ElasticsearchRepository<Article, String> { List<Article> findByTitle(String title); } ``` 5.在服务使用Elasticsearch存储库 在服务注入Elasticsearch存储库,并使用它执行Elasticsearch操作,例如: ```java @Service public class ArticleService { @Autowired private ArticleRepository articleRepository; public List<Article> search(String title) { return articleRepository.findByTitle(title); } public void save(Article article) { articleRepository.save(article); } } ``` 这是一个简单的示例,演示了如何在Spring Boot应用程序使用Elasticsearch。你可以使用这个示例作为起点,根据自己的需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

云淡风轻~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值