(十)ES 入门教程

(十)ES 入门教程本文为学习笔记 主要用于记录本人学习过程

大家好,欢迎来到IT知识分享网。

本文为学习笔记,主要用于记录本人学习过程。部分内容为转载!!!!.

1、 ElasticSearch介绍

1.1 介绍

(十)ES 入门教程

1.2原理与应用

1.2.1索引结构

下图是ElasticSearch的索引结构,下边黑色部分是物理结构,上边橙色部分是逻辑结构,逻辑结构也是为了更好的去描述ElasticSearch的工作原理及去使用物理结构中的索引文件

 

(十)ES 入门教程

(十)ES 入门教程

现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:

(十)ES 入门教程

两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

2 ElasticaSearch安装

2.1 安装

(十)ES 入门教程

2.2 配置文件

2.2.1 三个配置文件

ES的配置文件的地址根据安装形式的不同而不同:

 

(十)ES 入门教程

elasticsearch.yml : 用于配置Elasticsearch运行参数 jvm.options : 用于配置Elasticsearch JVM设置log4j2.properties: 用于配置Elasticsearch日志

2.2.2 elasticsearch.yml

例子如下

cluster.name: xuecheng #配置elasticsearch的集群名称,默认是elasticsearch。建议修改成一个有意义的名称。 node.name: xc_node_1 #节点名,通常一台物理服务器就是一个节点,es会默认随机指定一个名字,建议指定一个有意义的名称,方便管理 network.host: 0.0.0.0 #绑定ip地址 http.port: 9200 #暴露的http端口 transport.tcp.port: 9300 #内部端口 node.master: true #主节点 node.data: true #数据节点 discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301", "0.0.0.0:9302"] #设置集群中master节点的初始列表 discovery.zen.minimum_master_nodes: 1 #主结点数量的最少值 ,此值的公式为:(master_eligible_nodes / 2) + 1 ,比如:有3个符合要求的主结点,那么这里要设置为2。 bootstrap.memory_lock: false #内存的锁定只给es用 node.max_local_storage_nodes: 1 #单机允许的最大存储结点数,通常单机启动一个结点建议设置为1,开发环境如果单机启动多个节点可设置大于1 path.data: D:\ElasticSearch\elasticsearch‐6.2.1\data #索引目录 path.logs: D:\ElasticSearch\elasticsearch‐6.2.1\logs #日志 http.cors.enabled: true # 跨域设置 http.cors.allow‐origin: /.*/ 

2.2.3 jvm.options

2.2.4 log4j2.properties

日志文件设置,ES使用log4j,注意日志级别的配置。

2.3 启动ES

进入bin目录,在cmd下运行:elasticsearch.bat

(十)ES 入门教程

(十)ES 入门教程

2.4 head插件安装

(十)ES 入门教程

3 ES快速入门

ES作为一个索引及搜索服务,对外提供丰富的REST接口,快速入门部分的实例使用head插件来测试,目的是对ES的使用方法及流程有个初步的认识。

3.1 创建索引库

{ "settings":{ "index":{ "number_of_shards":1, "number_of_replicas":0 } } } 
  • 使用head插件创建  

    (十)ES 入门教程

     

(十)ES 入门教程

3.2 创建映射

3.2.1 概念说明

在索引中每个文档都包括了一个或多个field,创建映射就是向索引库中创建field的过程,下边是document和field与关系数据库的概念的类比:

3.2.2 创建映射

{ "properties": { "name": { "type": "text" }, "description": { "type": "text" }, "studymodel": { "type": "keyword" } } } 

映射创建成功,查看head界面:

(十)ES 入门教程

3.3 创建文档

{ "name":"Bootstrap开发框架", "description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。", "studymodel":"" } 

使用postman测试:

(十)ES 入门教程

通过head查询数据:

(十)ES 入门教程

3.4 搜索文档

(十)ES 入门教程

3.4.1查询结果分析

{ "took": 4, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "xc_course", "_type": "doc", "_id": "4028e58161bcf7f40161bcf8b77c0000", "_score": 1, "_source": { "name": "Bootstrap开发框架", "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。", "studymodel": "" } } ] } } 

4 IK分词器

4.1测试分词器

(十)ES 入门教程

会发现分词的效果将 “测试” 这个词拆分成两个单字“测”和“试”,这是因为当前索引库使用的分词器对中文就是单字分词。

4.2 安装IK分词器

(十)ES 入门教程

 

(十)ES 入门教程

4.3 两种分词模式

4.4 自定义词库

(十)ES 入门教程

打开IKAnalyzer.cfg.xml配置一下

(十)ES 入门教程

(十)ES 入门教程

(十)ES 入门教程

 

6 映射类型

6.1 映射维护方法

{ "properties": { "name": { "type": "text" }, "description": { "type": "text" }, "studymodel": { "type": "keyword" } } } 

6.2 常用映射类型

6.2.1 text文本字段

"name": { "type": "text", "analyzer":"ik_max_word" } 

上边指定了analyzer是指在索引和搜索都使用ik_max_word,如果单独想定义搜索时使用的分词器则可以通过search_analyzer属性。

对于ik分词器建议是索引时使用ik_max_word将搜索内容进行细粒度分词,搜索时使用ik_smart提高搜索精确性

"name": { "type": "text", "analyzer":"ik_max_word", "search_analyzer":"ik_smart" } 
pic": { "type": "text", "index":false } 

6.2.2 keyword关键字字段

上边介绍的text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜索,所以创建keyword字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword字段通常用于过虑、排序、聚合等。

6.2.3 date日期类型

{ "properties": { "timestamp": { "type": "date", "format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd" } } } 

6.2.4 数值类型

"price": { "type": "scaled_float", "scaling_factor": 100 } 

 

6索引管理

6.1 搭建工程

6.1.1 ES客户端

ES提供多种不同的客户端:

<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch‐rest‐high‐level‐client</artifactId> <version>6.2.1</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.2.1</version> </dependency> 

2、配置文件

server: port: ${port:40100} spring: application: name: xc-search-service xuecheng: elasticsearch: hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔 

3、配置类

@Configuration public class ElasticsearchConfig { @Value("${xuecheng.elasticsearch.hostlist}") private String hostlist; @Bean public RestHighLevelClient restHighLevelClient(){ //解析hostlist配置信息 String[] split = hostlist.split(","); //创建HttpHost数组,其中存放es主机和端口的配置信息 HttpHost[] httpHostArray = new HttpHost[split.length]; for(int i=0;i<split.length;i++){ String item = split[i]; httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http"); } //创建RestHighLevelClient客户端 return new RestHighLevelClient(RestClient.builder(httpHostArray)); } //项目主要使用RestHighLevelClient,对于低级的客户端暂时不用 @Bean public RestClient restClient(){ //解析hostlist配置信息 String[] split = hostlist.split(","); //创建HttpHost数组,其中存放es主机和端口的配置信息 HttpHost[] httpHostArray = new HttpHost[split.length]; for(int i=0;i<split.length;i++){ String item = split[i]; httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http"); } return RestClient.builder(httpHostArray).build(); } } 

6.2创建索引库

@SpringBootTest @RunWith(SpringRunner.class) public class TestSearch { @Autowired RestHighLevelClient restHighLevelClient; @Autowired RestClient restClient; @Test public void creatIndex() throws IOException { //创建索引请求对象 CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course"); //设置索引参数 createIndexRequest.settings(Settings.builder().put("number_of_shards",1).put("number_of_replicas",0)); //设置映射 createIndexRequest.mapping("doc","{\n" + "\"properties\": {\n" + "\"name\": {\n" + "\"type\": \"text\",\n" + "\"analyzer\":\"ik_max_word\",\n" + "\"search_analyzer\":\"ik_smart\"\n" + "},\n" + "\"description\": {\n" + "\"type\": \"text\",\n" + "\"analyzer\":\"ik_max_word\",\n" + "\"search_analyzer\":\"ik_smart\"\n" + "},\n" + "\"studymodel\": {\n" + "\"type\": \"keyword\"\n" + "},\n" + "\"price\": {\n" + "\"type\": \"float\"\n" + "},\n" + "\"timestamp\": {\n" + "\"type\": \"date\",\n" + "\"format\": \"yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis\"\n" + "}\n" + "}\n" + "}\n" ,XContentType.JSON); //链接客户端 IndicesClient client = restHighLevelClient.indices(); //创建响应对象 CreateIndexResponse response = client.create(createIndexRequest); //响应 boolean acknowledged = response.isAcknowledged(); System.out.println(acknowledged); } @Test public void delIndex() throws IOException { //创建索引请求对象 DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("xc_course"); //链接客户端 IndicesClient client = restHighLevelClient.indices(); //删除索引 DeleteIndexResponse delete = client.delete(deleteIndexRequest); //响应 boolean acknowledged = delete.isAcknowledged(); System.out.println(acknowledged); } } 

6.3 添加文档

 @Test public void addDoc() throws IOException { //准备json数据 Map<String, Object> jsonMap = new HashMap<>(); jsonMap.put("name", "spring cloud实战"); jsonMap.put("description", "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心eureka。"); jsonMap.put("studymodel", ""); SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy‐MM‐dd HH:mm:ss"); jsonMap.put("timestamp", dateFormat.format(new Date())); jsonMap.put("price", 5.6f); //创建索引请求对象 IndexRequest indexRequest = new IndexRequest("xc_course","doc"); //指定索引添加文档 indexRequest.source(jsonMap); //链接客户端 IndexResponse indexResponse = restHighLevelClient.index(indexRequest); //获取响应结果 DocWriteResponse.Result result = indexResponse.getResult(); System.out.println(result); } 

6.4 查询文档

 @Test public void queryDoc() throws IOException { GetRequest getRequest = new GetRequest("xc_course","doc","5W8t9WgBMHjTRj0U9GkF"); GetResponse getResponse = restHighLevelClient.get(getRequest); boolean exists = getResponse.isExists(); Map<String, Object> sourceAsMap = getResponse.getSourceAsMap(); System.out.println(sourceAsMap); } 

7搜索管理

7.1 准备环境

创建xc_course索引库。 创建映射: post:http://localhost:9200/xc_course/doc/_mapping { "properties": { "description": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "name": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "pic":{ "type":"text", "index":false }, "price": { "type": "float" }, "studymodel": { "type": "keyword" }, "timestamp": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } } } 初始化文档: http://localhost:9200/xc_course/doc/1 { "name": "Bootstrap开发", "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。", "studymodel": "", "price":38.6, "timestamp":"2018-04-25 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" } http://localhost:9200/xc_course/doc/2 { "name": "java编程基础", "description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。", "studymodel": "", "price":68.6, "timestamp":"2018-03-25 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" } http://localhost:9200/xc_course/doc/3 { "name": "spring开发基础", "description": "spring 在java领域非常流行,java程序员都在用。", "studymodel": "", "price":88.6, "timestamp":"2018-02-24 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" } 

7.3 DSL搜索

7.3.1 查询所有文档

@SpringBootTest @RunWith(SpringRunner.class) public class TestSearch { @Autowired RestHighLevelClient restHighLevelClient; @Autowired RestClient restClient; @Test public void queryAll() throws IOException, ParseException { //搜索请求对象 SearchRequest searchRequest = new SearchRequest("xc_course"); //指定类型 searchRequest.types("doc"); //搜索源构建对象 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //搜索方式 //matchAllQuery搜索全部 searchSourceBuilder.query(QueryBuilders.matchAllQuery()); //设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段 searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{}); //向搜索请求对象中设置搜索源 searchRequest.source(searchSourceBuilder); //执行搜索,向ES发起http请求 SearchResponse searchResponse = restHighLevelClient.search(searchRequest); //搜索结果 SearchHits hits = searchResponse.getHits(); //匹配到的总记录数 long totalHits = hits.getTotalHits(); //得到匹配度高的文档 SearchHit[] searchHits = hits.getHits(); //日期格式化对象 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for(SearchHit hit:searchHits){ //文档的主键 String id = hit.getId(); //源文档内容 Map<String, Object> sourceAsMap = hit.getSourceAsMap(); String name = (String) sourceAsMap.get("name"); //由于前边设置了源文档字段过虑,这时description是取不到的 String description = (String) sourceAsMap.get("description"); //学习模式 String studymodel = (String) sourceAsMap.get("studymodel"); //价格 Double price = (Double) sourceAsMap.get("price"); //日期 Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp")); System.out.println(name); System.out.println(studymodel); System.out.println(description); } } } 

7.3.2 分页查询

//分页查询 @Test public void testSearchPage() throws IOException, ParseException { //搜索请求对象 SearchRequest searchRequest = new SearchRequest("xc_course"); //指定类型 searchRequest.types("doc"); //搜索源构建对象 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //设置分页参数 //页码 int page = 1; //每页记录数 int size = 1; //计算出记录起始下标 int from = (page-1)*size; searchSourceBuilder.from(from);//起始记录下标,从0开始 searchSourceBuilder.size(size);//每页显示的记录数 //搜索方式 //matchAllQuery搜索全部 searchSourceBuilder.query(QueryBuilders.matchAllQuery()); //设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段 searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{}); //向搜索请求对象中设置搜索源 searchRequest.source(searchSourceBuilder); //执行搜索,向ES发起http请求 SearchResponse searchResponse = client.search(searchRequest); //搜索结果 SearchHits hits = searchResponse.getHits(); //匹配到的总记录数 long totalHits = hits.getTotalHits(); //得到匹配度高的文档 SearchHit[] searchHits = hits.getHits(); //日期格式化对象 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for(SearchHit hit:searchHits){ //文档的主键 String id = hit.getId(); //源文档内容 Map<String, Object> sourceAsMap = hit.getSourceAsMap(); String name = (String) sourceAsMap.get("name"); //由于前边设置了源文档字段过虑,这时description是取不到的 String description = (String) sourceAsMap.get("description"); //学习模式 String studymodel = (String) sourceAsMap.get("studymodel"); //价格 Double price = (Double) sourceAsMap.get("price"); //日期 Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp")); System.out.println(name); System.out.println(studymodel); System.out.println(description); } } 

7.3.3 Term Query

Term Query为精确查询,在搜索时会整体匹配关键字,不再将关键字分词

 //TermQuery @Test public void testTermQuery() throws IOException, ParseException { //搜索请求对象 SearchRequest searchRequest = new SearchRequest("xc_course"); //指定类型 searchRequest.types("doc"); //搜索源构建对象 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //设置分页参数 //页码 int page = 1; //每页记录数 int size = 1; //计算出记录起始下标 int from = (page-1)*size; searchSourceBuilder.from(from);//起始记录下标,从0开始 searchSourceBuilder.size(size);//每页显示的记录数 //搜索方式 //termQuery searchSourceBuilder.query(QueryBuilders.termQuery("name","spring")); //设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段 searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{}); //向搜索请求对象中设置搜索源 searchRequest.source(searchSourceBuilder); //执行搜索,向ES发起http请求 SearchResponse searchResponse = client.search(searchRequest); //搜索结果 SearchHits hits = searchResponse.getHits(); //匹配到的总记录数 long totalHits = hits.getTotalHits(); //得到匹配度高的文档 SearchHit[] searchHits = hits.getHits(); //日期格式化对象 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for(SearchHit hit:searchHits){ //文档的主键 String id = hit.getId(); //源文档内容 Map<String, Object> sourceAsMap = hit.getSourceAsMap(); String name = (String) sourceAsMap.get("name"); //由于前边设置了源文档字段过虑,这时description是取不到的 String description = (String) sourceAsMap.get("description"); //学习模式 String studymodel = (String) sourceAsMap.get("studymodel"); //价格 Double price = (Double) sourceAsMap.get("price"); //日期 Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp")); System.out.println(name); System.out.println(studymodel); System.out.println(description); } } 

7.3.4 根据id精确匹配

ES提供根据多个id值匹配的方法:

 //根据id查询 @Test public void testTermQueryByIds() throws IOException, ParseException { //搜索请求对象 SearchRequest searchRequest = new SearchRequest("xc_course"); //指定类型 searchRequest.types("doc"); //搜索源构建对象 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //搜索方式 //根据id查询 //定义id String[] ids = new String[]{"1","2"}; searchSourceBuilder.query(QueryBuilders.termsQuery("_id",ids)); //设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段 searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{}); //向搜索请求对象中设置搜索源 searchRequest.source(searchSourceBuilder); //执行搜索,向ES发起http请求 SearchResponse searchResponse = client.search(searchRequest); //搜索结果 SearchHits hits = searchResponse.getHits(); //匹配到的总记录数 long totalHits = hits.getTotalHits(); //得到匹配度高的文档 SearchHit[] searchHits = hits.getHits(); //日期格式化对象 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for(SearchHit hit:searchHits){ //文档的主键 String id = hit.getId(); //源文档内容 Map<String, Object> sourceAsMap = hit.getSourceAsMap(); String name = (String) sourceAsMap.get("name"); //由于前边设置了源文档字段过虑,这时description是取不到的 String description = (String) sourceAsMap.get("description"); //学习模式 String studymodel = (String) sourceAsMap.get("studymodel"); //价格 Double price = (Double) sourceAsMap.get("price"); //日期 Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp")); System.out.println(name); System.out.println(studymodel); System.out.println(description); } } 

7.3.9 排序

可以在字段上添加一个或多个排序,支持在keyword、date、float等类型上添加,text类型的字段上不允许添加排序。

 //Sort @Test public void testSort() throws IOException, ParseException { //搜索请求对象 SearchRequest searchRequest = new SearchRequest("xc_course"); //指定类型 searchRequest.types("doc"); //搜索源构建对象 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //boolQuery搜索方式 //定义一个boolQuery BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); //定义过虑器 boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100)); searchSourceBuilder.query(boolQueryBuilder); //添加排序 searchSourceBuilder.sort("studymodel", SortOrder.DESC); searchSourceBuilder.sort("price", SortOrder.ASC); //设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段 searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{}); //向搜索请求对象中设置搜索源 searchRequest.source(searchSourceBuilder); //执行搜索,向ES发起http请求 SearchResponse searchResponse = client.search(searchRequest); //搜索结果 SearchHits hits = searchResponse.getHits(); //匹配到的总记录数 long totalHits = hits.getTotalHits(); //得到匹配度高的文档 SearchHit[] searchHits = hits.getHits(); //日期格式化对象 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for(SearchHit hit:searchHits){ //文档的主键 String id = hit.getId(); //源文档内容 Map<String, Object> sourceAsMap = hit.getSourceAsMap(); String name = (String) sourceAsMap.get("name"); //由于前边设置了源文档字段过虑,这时description是取不到的 String description = (String) sourceAsMap.get("description"); //学习模式 String studymodel = (String) sourceAsMap.get("studymodel"); //价格 Double price = (Double) sourceAsMap.get("price"); //日期 Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp")); System.out.println(name); System.out.println(studymodel); System.out.println(description); } }

原文链接1:https://www.jianshu.com/p/403c9d5b1463

原文链接2:https://www.jianshu.com/p/7d687c9dba4f

原文链接3:https://www.jianshu.com/p/3cb80dcf514f

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/119605.html

(0)
上一篇 2025-11-04 11:33
下一篇 2025-11-04 12:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信