【ElasticSearch 学习笔记】Java API

学习视频:ElasticSearch 教程入门到精通(基于 ELK 技术栈 elasticsearch 7.8.x 版本)

学习大纲:

  • 第 1 章 Elasticsearch 概述
  • 第 2 章 Elasticsearch 入门
  • 第 3 章 Elasticsearch 环境
  • 第 4 章 Elasticsearch 进阶
  • 第 5 章 Elasticsearch 集成
  • 第 6 章 Elasticsearch 优化
  • 第 7 章 Elasticsearch 面试题

第 2 章 ElasticSearch 入门

JavaAPI - 环境准备

新建一个 Maven 工程,添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>7.8.0</version>
    </dependency>
    <!-- elasticsearch 的客户端 -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.8.0</version>
    </dependency>
    <!-- elasticsearch 依赖 2.x 的 log4j -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.8.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.8.2</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.9</version>
    </dependency>
    <!-- junit 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

HelloElasticsearch

import java.io.IOException;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class HelloElasticsearch {

	public static void main(String[] args) throws IOException {
		// 创建客户端对象
		RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(new HttpHost("localhost", 9200, "http")));
		//		...
		System.out.println(client);

		// 关闭客户端连接
		client.close();
	}
}

JavaAPI - 索引操作

创建索引

import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

import java.io.IOException;

public class CreateIndex {
    public static void main(String[] args) throws IOException {
        // 创建客户端对象
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200, "http")));

        // 创建索引 - 请求对象
        CreateIndexRequest request = new CreateIndexRequest("user");
        // 发送请求,获取响应
        CreateIndexResponse response = client.indices()
                .create(request, RequestOptions.DEFAULT);
        boolean acknowledged = response.isAcknowledged();
        // 响应状态
        System.out.println("操作状态 = " + acknowledged);

        // 关闭客户端连接
        client.close();
    }
}

查询索引

public class SearchIndex {
    public static void main(String[] args) throws IOException {
        // 创建客户端对象
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200, "http")));

        // 查询索引 - 请求对象
        GetIndexRequest request = new GetIndexRequest("user");
        // 发送请求,获取响应
        GetIndexResponse response = client.indices()
          .get(request, RequestOptions.DEFAULT);
        System.out.println("aliases:" + response.getAliases());
        System.out.println("mappings:" + response.getMappings());
        System.out.println("settings:" + response.getSettings());

        client.close();
    }
}

控制台输出:

aliases:{user=[]}
mappings:{user=org.elasticsearch.cluster.metadata.MappingMetadata@e9e63037}
settings:{user={"index.creation_date":"1646888783599","index.number_of_replicas":"1","index.number_of_shards":"1","index.provided_name":"user","index.uuid":"uN9XtaJBT2O_sjwMdH06MQ","index.version.created":"7080099"}}

删除索引

public class DeleteIndex {
    public static void main(String[] args) throws IOException {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200, "http")));
        // 删除索引 - 请求对象
        DeleteIndexRequest request = new DeleteIndexRequest("user");
        // 发送请求,获取响应
        AcknowledgedResponse response = client.indices()
          .delete(request, RequestOptions.DEFAULT);
        // 操作结果
        System.out.println("操作结果:" + response.isAcknowledged());
        client.close();
    }
}

控制台输出:

操作结果:true

JavaAPI - 文档基本操作

重构

对 ElasticSearch 的连接和关闭进行重构:

import org.elasticsearch.client.RestHighLevelClient;

@FunctionalInterface
public interface ElasticsearchTask {
    void doSomething(RestHighLevelClient client) throws Exception;
}
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class ConnectElasticsearch {
    public static void connect(ElasticsearchTask task) {
        // 创建客户端对象,并自动关闭连接
        try (RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200, "http")))) {
            task.doSomething(client);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

想要 ElasticSearch 完成一些操作,只要编写一个 lambda 即可:

public class SomeClass {

    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
						//do something
        });
    }
}

新增文档

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yu.es.test.model.User;
import com.yu.es.test.utils.ConnectElasticsearch;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;

public class InsertDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 新增文档 - 请求对象
            IndexRequest request = new IndexRequest();
            // 设置索引及唯一性标识
            request.index("user").id("1001");

            // 创建数据对象
            User user = new User();
            user.setName("zhangsan");
            user.setAge(30);
            user.setSex("男");

            // Model -> JSON
            ObjectMapper objectMapper = new ObjectMapper();
            String productJson = objectMapper.writeValueAsString(user);
            // 添加文档数据,数据格式为 JSON 格式
            request.source(productJson, XContentType.JSON);
            // 客户端发送请求,获取响应对象
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);

            // 打印结果信息
            System.out.println("_index:" + response.getIndex());
            System.out.println("_id:" + response.getId());
            System.out.println("_result:" + response.getResult());
        });
    }
}

控制台输出:

_index:user
_id:1001
_result:CREATED

修改文档

import com.yu.es.test.utils.ConnectElasticsearch;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;

public class UpdateDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 修改文档 - 请求对象
            UpdateRequest request = new UpdateRequest();
            // 配置修改参数
            request.index("user").id("1001");
            // 设置请求体,对数据进行修改
            request.doc(XContentType.JSON, "sex", "女");
            // 客户端发送请求,获取响应对象
            UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
            System.out.println("_index:" + response.getIndex());
            System.out.println("_id:" + response.getId());
            System.out.println("_result:" + response.getResult());
        });
    }
}

控制台输出:

_index:user
_id:1001
_result:UPDATED

查询文档

import com.yu.es.test.utils.ConnectElasticsearch;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.RequestOptions;

public class GetDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 1.创建请求对象
            GetRequest request = new GetRequest().index("user").id("1001");
            // 2.客户端发送请求,获取响应对象
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
            // 3.打印结果信息
            System.out.println("_index:" + response.getIndex());
            System.out.println("_type:" + response.getType());
            System.out.println("_id:" + response.getId());
            System.out.println("source:" + response.getSourceAsString());
        });
    }
}

控制台输出:

_index:user
_type:_doc
_id:1001
source:{"name":"zhangsan","sex":"女","age":30}

删除文档

import com.yu.es.test.utils.ConnectElasticsearch;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.RequestOptions;

public class DeleteDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 1.创建请求对象
            DeleteRequest request = new DeleteRequest().index("user").id("1001");
            // 2.客户端发送请求,获取响应对象
            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
            // 3.打印信息
            System.out.println(response.toString());
        });
    }
}

控制台输出:

DeleteResponse[index=user,type=_doc,id=1001,version=3,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]

批量新增

import com.yu.es.test.utils.ConnectElasticsearch;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;

public class BatchInsertDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 创建批量新增请求对象
            BulkRequest request = new BulkRequest();
            request.add(new IndexRequest().index("user").id("1001")
                    .source(XContentType.JSON, "name", "zhangsan", "age", 30));
            request.add(new IndexRequest().index("user").id("1002")
                    .source(XContentType.JSON, "name", "lisi"));
            request.add(new IndexRequest().index("user").id("1003")
                    .source(XContentType.JSON, "name", "wangwu"));
            // 客户端发送请求,获取响应对象
            BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
            // 打印结果信息
            System.out.println("took:" + responses.getTook());
            System.out.println("items:" + responses.getItems());
        });
    }
}

批量删除

import com.yu.es.test.utils.ConnectElasticsearch;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.client.RequestOptions;

public class BatchDelDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 创建批量删除请求对象
            BulkRequest request = new BulkRequest();
            request.add(new DeleteRequest().index("user").id("1001"));
            request.add(new DeleteRequest().index("user").id("1002"));
            request.add(new DeleteRequest().index("user").id("1003"));
            // 客户端发送请求,获取响应对象
            BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
            // 打印结果信息
            System.out.println("took:" + responses.getTook());
            System.out.println("items:" + responses.getItems());
        });
    }
}

JavaAPI - 文档高级查询

注意:

  • matchQuery 分词搜索
  • termQuery 完全匹配搜索

先批量新增数据:

public class BatchInsertDoc {
    public static void main(String[] args) {
        ConnectElasticsearch.connect(client -> {
            // 创建批量新增请求对象
            BulkRequest request = new BulkRequest();
            request.add(new IndexRequest().index("user").id("1001")
                    .source(XContentType.JSON, "name", "zhangsan", "age", "10", "sex", "女"));
            request.add(new IndexRequest().index("user").id("1002")
                    .source(XContentType.JSON, "name", "lisi", "age", "30", "sex", "女"));
            request.add(new IndexRequest().index("user").id("1003")
                    .source(XContentType.JSON, "name", "wangwu1", "age", "40", "sex", "男"));
            request.add(new IndexRequest().index("user").id("1004")
                    .source(XContentType.JSON, "name", "wangwu2", "age", "20", "sex", "女"));
            request.add(new IndexRequest().index("user").id("1005")
                    .source(XContentType.JSON, "name", "wangwu3", "age", "50", "sex", "男"));
            request.add(new IndexRequest().index("user").id("1006")
                    .source(XContentType.JSON, "name", "wangwu4", "age", "20", "sex", "男"));
            // 客户端发送请求,获取响应对象
            BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
            // 打印结果信息
            System.out.println("took:" + responses.getTook());
            System.out.println("items:" + responses.getItems());
        });
    }
}

封装

以下操作基本模板都是一样的,不同的就是各种查询的构建过程不同:

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("user");

// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

// 构建....
// 这一块的代码需要根据具体需求来写

// 输出查询结果
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut()); // 是否超时
System.out.println("total:" + hits.getTotalHits()); // 查询到数据总数
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
// 输出每条查询的结果信息
for (SearchHit hit : hits)
		System.out.println(hit.getSourceAsString());
System.out.println("<<========");

因此我们将该过程封装一下:

public class QueryDoc {
    @FunctionalInterface
    public interface QueryBuild {
        void doBuild(SearchRequest request, SearchSourceBuilder sourceBuilder) throws IOException;
    }

    public static void doQuery(RestHighLevelClient client, QueryBuild build) throws IOException {
        // 创建搜索请求对象
        SearchRequest request = new SearchRequest();
        request.indices("user");
        // 构建查询的请求体
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 请求体的构建由调用者进行
        build.doBuild(request, sourceBuilder);
        request.source(sourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 结果输出
        SearchHits hits = response.getHits();
        System.out.println("took:" + response.getTook());
        System.out.println("timeout:" + response.isTimedOut());
        System.out.println("total:" + hits.getTotalHits());
        System.out.println("MaxScore:" + hits.getMaxScore());
        System.out.println("hits========>>");
        // 输出每条查询的结果信息
        for (SearchHit hit : hits)
            System.out.println(hit.getSourceAsString());
        System.out.println("<<========");
    }
}

使用方法:(后面的示例省略 main 方法中调用)

public static final ElasticsearchTask SEARCH_ALL = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    // 全量查询
    sourceBuilder.query(QueryBuilders.matchAllQuery());
  });
};

public static void main(String[] args) {
		ConnectElasticsearch.connect(SEARCH_ALL); // 全量查询
}

全量查询

示例:查询所有索引数据

public static final ElasticsearchTask SEARCH_ALL = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    // 查询全部
    sourceBuilder.query(QueryBuilders.matchAllQuery());
  });
};
took:2ms
timeout:false
total:6 hits
MaxScore:1.0
hits========>>
{"name":"zhangsan","age":"10","sex":"女"}
{"name":"lisi","age":"30","sex":"女"}
{"name":"wangwu1","age":"40","sex":"男"}
{"name":"wangwu2","age":"20","sex":"女"}
{"name":"wangwu3","age":"50","sex":"男"}
{"name":"wangwu4","age":"20","sex":"男"}
<<========

条件查询

示例:查询年龄为 30 的索引数据

public static final ElasticsearchTask SEARCH_BY_CONDITION = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    // 查询条件:age = 30
    sourceBuilder.query(QueryBuilders.termQuery("age", "30"));
  });
};
took:10ms
timeout:false
total:1 hits
MaxScore:1.0
hits========>>
{"name":"lisi","age":"30","sex":"女"}
<<========

分页查询

示例:每页 2 条数据,查询第 1 页的数据

public static final ElasticsearchTask SEARCH_BY_PAGING = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    // 当前页起始索引(第一条数据的顺序号) from
    sourceBuilder.from(0);
    // 每页显示多少条 size
    sourceBuilder.size(2);
  });
};
took:2ms
timeout:false
total:6 hits
MaxScore:1.0
hits========>>
{"name":"zhangsan","age":"10","sex":"女"}
{"name":"lisi","age":"30","sex":"女"}
<<========

查询排序

public static final ElasticsearchTask SEARCH_WITH_ORDER = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    // 查询全部
    sourceBuilder.query(QueryBuilders.matchAllQuery());
    // 年龄升序
    sourceBuilder.sort("age", SortOrder.ASC);
  });
};
took:57ms
timeout:false
total:6 hits
MaxScore:NaN
hits========>>
{"name":"zhangsan","age":"10","sex":"女"}
{"name":"wangwu2","age":"20","sex":"女"}
{"name":"wangwu4","age":"20","sex":"男"}
{"name":"lisi","age":"30","sex":"女"}
{"name":"wangwu1","age":"40","sex":"男"}
{"name":"wangwu3","age":"50","sex":"男"}
<<========

组合查询

public static final ElasticsearchTask SEARCH_BY_BOOL_CONDITION = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // 必须包含: age = 30
    boolQueryBuilder.must(QueryBuilders.matchQuery("age", "30"));
    // 一定不含:name = zhangsan
    boolQueryBuilder.mustNot(QueryBuilders.matchQuery("name", "zhangsan"));
    // 可能包含: sex = 男
    boolQueryBuilder.should(QueryBuilders.matchQuery("sex", "男"));
    sourceBuilder.query(boolQueryBuilder);
  });
};
took:2ms
timeout:false
total:1 hits
MaxScore:1.0
hits========>>
{"name":"lisi","age":"30","sex":"女"}
<<========

范围查询

public static final ElasticsearchTask SEARCH_BY_RANGE = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
    // rangeQuery.gte("30"); // age 大于等于 30
    rangeQuery.lte("40"); // age 小于等于 40
    sourceBuilder.query(rangeQuery);
  });
};
took:7ms
timeout:false
total:5 hits
MaxScore:1.0
hits========>>
{"name":"zhangsan","age":"10","sex":"女"}
{"name":"lisi","age":"30","sex":"女"}
{"name":"wangwu1","age":"40","sex":"男"}
{"name":"wangwu2","age":"20","sex":"女"}
{"name":"wangwu4","age":"20","sex":"男"}
<<========

模糊查询

public static final ElasticsearchTask SEARCH_BY_FUZZY_CONDITION = client -> {
  doQuery(client, (request, sourceBuilder) -> {
    // 模糊查询: name 包含 wangwu 的数据
    sourceBuilder.query(
      QueryBuilders.fuzzyQuery("name", "wangwu").fuzziness(Fuzziness.ONE));
  });
};
took:5ms
timeout:false
total:4 hits
MaxScore:1.2837042
hits========>>
{"name":"wangwu1","age":"40","sex":"男"}
{"name":"wangwu2","age":"20","sex":"女"}
{"name":"wangwu3","age":"50","sex":"男"}
{"name":"wangwu4","age":"20","sex":"男"}
<<========

高亮查询

public static final ElasticsearchTask SEARCH_WITH_HIGHLIGHT = client -> {
  SearchRequest request = new SearchRequest().indices("user");
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  // 构建查询方式:高亮查询
  TermsQueryBuilder termsQueryBuilder =
    QueryBuilders.termsQuery("name", "zhangsan");
  // 设置查询方式
  sourceBuilder.query(termsQueryBuilder);
  // 构建高亮字段
  HighlightBuilder highlightBuilder = new HighlightBuilder();
  highlightBuilder.preTags("<font color='red'>"); // 设置标签前缀
  highlightBuilder.postTags("</font>"); // 设置标签后缀
  highlightBuilder.field("name"); // 设置高亮字段
  // 设置高亮构建对象
  sourceBuilder.highlighter(highlightBuilder);
  // 设置请求体
  request.source(sourceBuilder);
  // 客户端发送请求,获取响应对象
  SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  SearchHits hits = response.getHits();

  for (SearchHit hit : hits) {
    String sourceAsString = hit.getSourceAsString();
    System.out.println(sourceAsString);
    // 打印高亮结果
    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    System.out.println(highlightFields);
  }
};
{"name":"zhangsan","age":"10","sex":"女"}
{name=[name], fragments[[<font color='red'>zhangsan</font>]]}

最大值查询

public static final ElasticsearchTask SEARCH_WITH_MAX = client -> {
  SearchRequest request = new SearchRequest().indices("user");

  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.aggregation(AggregationBuilders.max("maxAge").field("age"));
  request.source(sourceBuilder);

  // 客户端发送请求,获取响应对象
  SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  System.out.println(response);
};
{
  ..., // 省略
	"aggregations": {
		"max#maxAge": {
			"value": 50.0
		}
	}
}

分组查询

public static final ElasticsearchTask SEARCH_WITH_GROUP = client -> {
  SearchRequest request = new SearchRequest().indices("user");

  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.aggregation(AggregationBuilders.terms("age_groupby").field("age"));
  request.source(sourceBuilder);

  // 客户端发送请求,获取响应对象
  SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  System.out.println(response);
};
{
  ..., // 省略
	"aggregations": {
		"lterms#age_groupby": {
			"doc_count_error_upper_bound": 0,
			"sum_other_doc_count": 0,
			"buckets": [{
				"key": 20,
				"doc_count": 2
			}, {
				"key": 10,
				"doc_count": 1
			}, {
				"key": 30,
				"doc_count": 1
			}, {
				"key": 40,
				"doc_count": 1
			}, {
				"key": 50,
				"doc_count": 1
			}]
		}
	}
}
  • 36
    点赞
  • 179
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

萌宅鹿同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值