Elasticsearch高级操作及集群
admin
2024-03-13 04:10:36

Elasticsearch高级操作及集群

1 Elastic Search 高级操作

昨天我们学习了,对于Elastic Search基本的索引,映射,文档相关的增删改查操作。但是对于Elastic Search还有一些更为复杂的高级操作。

1.1 批量操作

通过批量操作,我们可以一次向Elastic Search发送多条增删改查操作。从而达到一定程度上节省带宽的操作

1.1.1 基本语法

  • 先来看通过脚本的方式执行批量操作:
# 准备工作定义索引,及其映射
PUT teacher
{"mappings": {"properties": {"id": {"type": "long"},"name": {"type": "text"},"age": {"type": "integer"}}}
}

批量操作脚本

#批量操作
#新增1号
#新增2号
#更新2号
#删除2号
POST _bulk
{"create": {"_index": "teacher", "_id": "2"}}
{"id": 100, "name": "南风", "age": 18}
{"create": {"_index": "teacher", "_id": "2"}}
{"id": 101,"name": "长风", "age": 18}
{"update": {"_index": "teacher", "_id": "2"}}
{"doc": {"name":"景天", "age": 19}}
{"delete": {"_index":"teacher", "_id": "2"}}
  • 接着我们使用JAVA API执行批量操作
 /***  Bulk 批量操作*/@Testpublic void test2() throws IOException {//创建bulkrequest对象,整合所有操作BulkRequest bulkRequest =new BulkRequest();/*# 1. 删除1号记录# 2. 添加2号记录# 3. 修改2号记录 名称为 “景天”*///添加对应操作//添加1号记录Map firstMap=new HashMap<>();firstMap.put("name","南风");firstMap.put("age",18);IndexRequest indexRequest1=new IndexRequest("teacher").id("2").source(map);bulkRequest.add(indexRequest1);//添加2号记录Map secondMap=new HashMap<>();secondMap.put("name","长风");secondMap.put("age", 19);IndexRequest indexRequest2=new IndexRequest("teacher").id("2").source(map);bulkRequest.add(indexRequest2);//3. 修改3号记录 名称为 “景天”,年龄为19Map mapUpdate=new HashMap<>();mapUpdate.put("name","景天");UpdateRequest updateRequest=new UpdateRequest("teacher","2").doc(mapUpdate);bulkRequest.add(updateRequest);// 删除2号记录DeleteRequest deleteRequest=new DeleteRequest("teacher","2");bulkRequest.add(deleteRequest);//执行批量操作BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);System.out.println(response.status());}

1.1.2 批量从数据库导入数据

  • 创建索引和映射
PUT product
{"mappings": {"properties": {"id":{"type": "long"},"image": {"type":  "keyword"},"status": {"type": "integer"},"sellPoint": {"type": "text","analyzer": "ik_max_word"},"title": {"type": "text","analyzer": "ik_max_word"},"num":{"type": "integer"},"cid": {"type": "long"},"price": {"type": "double"},"limitNum": {"type": "integer"},"created": {"type": "date"},"updated": {"type": "date"}}}
}
  • 代码实现从数据库批量将数据导入Elastic Search
 /*** 从Mysql 批量导入 elasticSearch*/@Testpublic void test3() throws IOException {//1.查询所有数据,mysqlList products = productMapper.findAll();//2.bulk导入BulkRequest bulkRequest=new BulkRequest();//2.1 循环goodsList,创建IndexRequest添加数据for (Product product : products) {//将product对象转换为json字符串String data = JSON.toJSONString(product);IndexRequest indexRequest=new IndexRequest("product").source(data,XContentType.JSON);bulkRequest.add(indexRequest);}BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);System.out.println(response.status());}

1.2 高级查询

1.2.1 match all 查询

match all查询,相当于不加查询条件的查询索引中所有的文档

GET product/_search
{"query": {"match_all": {}},"from": 0,"size": 100
}
/*** 查询所有*  1. matchAll*  2. 将查询结果封装为Goods对象,装载到List中*  3. 分页。默认显示10条*/@Testpublic void matchAll() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest=new SearchRequest("product");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder sourceBuilder=new SearchSourceBuilder();//6. 查询条件QueryBuilder queryBuilder= QueryBuilders.matchAllQuery();//5. 指定查询条件sourceBuilder.query(queryBuilder);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(sourceBuilder);// 8 . 添加分页信息  不设置 默认10条
//        sourceBuilder.from(0);
//        sourceBuilder.size(100);//1. 查询,获取查询结果SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits hits = searchResponse.getHits();//7.1 获取总记录数Long total= hits.getTotalHits().value;System.out.println("总数:"+total);//7.2 获取Hits数据  数组SearchHit[] result = hits.getHits();//获取json字符串格式的数据List products = new ArrayList<>();for (SearchHit searchHit : result) {String sourceAsString = searchHit.getSourceAsString();//转为java对象Product product = JSON.parseObject(sourceAsString, Product.class);products.add(product);}System.out.println(items);}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOdQjUqA-1669974393059)(Elastic Search2.assets/match-all.png)]

1.2.2 term 查询

term查询和字段类型有关系,首先回顾一下ElasticSearch两个数据类型

ElasticSearch两个数据类型:

  • text:会分词,不支持聚合
  • keyword:不会分词,将全部内容作为一个词条,支持聚合

term查询:不会对查询条件进行分词。但是注意,term查询,查询text类型字段时,文档中类型为text类型的字段本身仍然会分词

GET product/_search
{"query": {"term": {"title": {"value": "手机充电器"}}}
}

Java API

@Testpublic void testTerm() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest = new SearchRequest("product");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//6. 查询条件TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "手机充电器");//5. 指定查询条件searchSourceBuilder.query(termQueryBuilder);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(searchSourceBuilder);//1. 查询,获取查询结果SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits hits = search.getHits();//7.1 获取总记录数long value = hits.getTotalHits().value;ArrayList items = new ArrayList<>();SearchHit[] h = hits.getHits();for (int i = 0; i < value; i++) {Product product = JSON.parseObject(h[i].getSourceAsString(), Item.class);items.add(product);}System.out.println(items);}

1.2.3 match查询

match查询的特征:

•会对查询条件进行分词。

•然后将分词后的查询条件和目标字段分词后的词条进行等值匹配

•默认取并集(OR),即只要查询条件中的一个分词和目标字段值的一个分词(词条)匹配,即认为匹配查询条件

# match查询
GET product/_search
{"query": {"match": {"title": "手机充电器"}},"size": 500
}

match 的默认搜索(or 并集)例如:华为手机,会分词为 “华为”,“手机” 只要出现其中一个词条都会认为词条匹配

match的 and(交集) 搜索,例如:例如:华为手机,会分词为 “华为”,“手机” 但要求“华为”,和“手机”同时出现在词条中,才算词条匹配

GET product/_search
{"query": {"match": {"title": {"query": "华为手机","operator": "and"}}},"size": 500
}

Java API

    @Testpublic void testMatch() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest = new SearchRequest("product");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//6. 查询条件MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机充电器");// 设置关键字查询的运算符//matchQueryBuilder.operator(Operator.AND);//5. 指定查询条件searchSourceBuilder.query(matchQueryBuilder);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(searchSourceBuilder);//1. 查询,获取查询结果SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits hits = search.getHits();//7.1 获取总记录数long value = hits.getTotalHits().value;ArrayList items = new ArrayList<>();SearchHit[] h = hits.getHits();for (int i = 0; i < value; i++) {Product product = JSON.parseObject(h[i].getSourceAsString(), Product.class);items.add(product);}System.out.println(items);}

1.2.4 模糊查询

1.2.4.1 wildcard查询

wildcard查询: wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)

# wildcard 查询。查询条件分词,模糊查询
GET product/_search
{"query": {"wildcard": {"title": {"value": "手机*"}}}
}

1.2.4.2 正则查询

[A-Z a-z 0-9_] 表示一个大小写英文字符,或者0-9的数字字符,或者下划线字符_+号多次出现(.)*为任意字符
正则查询取决于正则表达式的效率
GET product/_search
{"query": {"regexp": {"title": "[A-Z a-z 0-9_]+(.)*"}}
}

1.2.4.3 前缀查询

对keyword类型支持比较好

# 前缀查询 对keyword类型支持比较好
GET product/_search
{"query": {"prefix": {"brandName": {"value": "三"}}}
}

1.2.4.4 模糊查询Java API

//模糊查询
WildcardQueryBuilder wildQuery = QueryBuilders.wildcardQuery("title", "充电*");//充电后多个字符
//正则查询RegexpQueryBuilder regexQuery = QueryBuilders.regexpQuery("title", "[A-Z a-z 0-9_]+(.)*");//前缀查询PrefixQueryBuilder prefixQuery = QueryBuilders.prefixQuery("title", "充电");

1.2.5 querystring

queryString 多条件查询

  1. 会对查询条件进行分词。
  2. 然后将分词后的查询条件和词条进行等值匹配
  3. 默认取并集(OR)
  4. 可以指定多个查询字段

query_string:可以识别query中的连接符(or 、and)

# queryStringGET product/_search
{"query": {"query_string": {"fields": ["title","sellPoint"], "query": "耳机 AND 充电器"}}
}

java代码

QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("耳机充电器").field("title").field("sellPoint");

simple_query_string:不识别query中的连接符(or 、and),查询时会将 “耳机”、“and”、“充电器”当做普通的查询内容来查询

GET product/_search
{"query": {"simple_query_string": {"fields": ["title","sellPoint"], "query": "耳机 AND 充电器"}}
}

java代码

QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("耳机充电器").field("title").field("sellPoint")

1.2.6 范围 & 排序查询

GET product/_search
{"query": {"range": {"price": {"gte": 100,"lte": 1000}}},"sort": [{"price": {"order": "desc"}}]
}
 //范围查询 以price 价格为条件
RangeQueryBuilder query = QueryBuilders.rangeQuery("price");//指定下限
query.gte(100);
//指定上限
query.lte(1000);sourceBuilder.query(query);//排序  价格 降序排列
sourceBuilder.sort("price",SortOrder.DESC);

1.2.7 复合查询 bool

boolQuery:对多个查询条件连接。其组成主要分为如下四个部分:

  1. must(and):条件必须成立
  2. must_not(not):条件必须不成立
  3. should(or):条件可以成立
  4. filter:条件必须成立,性能比must高。不会计算得分
# must
GET product/_search
{"query": {"bool": {"must": [{"term": {"title": {"value": "充电器"}}},{"match": {"sellPoint": "快充"}}]}}
}
# must_not
GET product/_search
{"query": {"bool": {"must_not": [{"match": {"title": "充电器"}}]}}
}
# should 中的多个条件是or关系
GET product/_search
{"query": {"bool": {"should": [{"term": {"title": {"value": "充电器"}}},{"term": {"sellPoint": {"value": "小菜鸡"}}}]}}
}
# filter
GET product/_search
{"query": {"bool": {"filter": [{"term": {"title": {"value": "充电器"}}},{"match": {"sellPoint": "快充"}}]}}
}

这里有几点需要注意:

  • 一个复合查询中,可以同时包含must,must not,should,filter中的一个或多个部分
  • 每一部分,都可以包含多个查询条件(只有should中的多个查询条件是or关系)
  • 当存在must,或者filter的时候,should中的条件默认不生效
  • must和filter都可以表示同时满足多个条件的查询,但是不同的地方在于must会计算文档的近似度得分,filter不会(must_not也不会)
# boolquery 包含多个部分
GET product/_search
{"query": {"bool": {"must": [{"term": {"title": {"value": "充电器"}}}],"filter":[ {"term": {"title": "原装"}},{"range":{"price": {"gte": 40,"lte": 100}}}]}}
}

JAVA API:

布尔查询:boolQuery

  1. 查询商品为(title): 充电器
  2. 查询过滤条件:原装
  3. 查询价格在:40-100
        //1.构建boolQueryBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//2.构建各个查询条件//2.1 查询品牌名称为:华为TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "耳机");boolQuery.must(termQueryBuilder);//2.2. 查询标题包含:手机MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", "原装");boolQuery.filter(matchQuery);//2.3 查询价格在:40-100RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");rangeQuery.gte(40);rangeQuery.lte(100);boolQuery.filter(rangeQuery);sourceBuilder.query(boolQuery);

1.2.8 聚合查询

聚合查询分为两种类型:

  • 指标聚合:相当于MySQL的聚合函数。max、min、avg、sum等
  • 桶聚合:相当于MySQL的 group by 操作。不要对text类型的数据进行分组,会失败。
# 聚合查询# 指标聚合 聚合函数GET product/_search
{"query": {"match": {"title": "耳机"}},"aggs": {"max_price": {"max": {"field": "price"}}}
}# 桶聚合  分组
GET product/_search
{"query": {"match": {"title": "充电器"}},"aggs": {"price_bucket": {"terms": {"field": "price","size": 100}}}
}

JAVA API

/*** 聚合查询:桶聚合,分组查询* 1. 查询title包含充电器的数据* 2. 查询充电器的价格列表*/
@Test
public void testAggQuery() throws IOException {SearchRequest searchRequest=new SearchRequest("product");SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "充电器");sourceBuilder.query(queryBuilder);// 查询价格列表  只展示前100条AggregationBuilder aggregation=AggregationBuilders.terms("price_bucket").field("price").size(100);sourceBuilder.aggregation(aggregation);searchRequest.source(sourceBuilder);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);//7 获取命中对象 SearchHitsSearchHits hits = searchResponse.getHits();//7.1 获取总记录数Long total= hits.getTotalHits().value;System.out.println("总数:"+total);// aggregations 对象Aggregations aggregations = searchResponse.getAggregations();//将aggregations 转化为mapMap aggregationMap = aggregations.asMap();//通过key获取price_bucket 对象 使用Aggregation的子类接收  buckets属性在Terms接口中体现// Aggregation price_bucket = aggregationMap.get("price_bucket");Terms price_bucket =(Terms) aggregationMap.get("price_bucket");//获取buckets 数组集合List buckets = goods_brands.getBuckets();Mapmap=new HashMap<>();//遍历buckets   key 属性名,doc_count 统计聚合数for (Terms.Bucket bucket : buckets) {System.out.println(bucket.getKey());map.put(bucket.getKeyAsString(),bucket.getDocCount());}System.out.println(map);}

1.2.9-高亮查询

高亮三要素:

  1. 高亮字段
  2. 前缀
  3. 后缀

默认前后缀 :em

手机
GET product/_search
{"query": {"match": {"title": "充电器"}},"highlight": {"fields": {"title": {"pre_tags": "","post_tags": ""}}}
}
 @Testpublic void testHighLight() throws IOException {// 构建针对索引"product"的查询请求SearchRequest searchRequest = new SearchRequest("prouct");// 创建SearchSourceBuilderSearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "充电器");searchSourceBuilder.query(matchQueryBuilder);// 构造高亮查询条件HighlightBuilder highlighter = new HighlightBuilder();highlighter.field("title").preTags("").postTags("");searchSourceBuilder.highlighter(highlighter);searchRequest.source(searchSourceBuilder);SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits hits = search.getHits();long value = hits.getTotalHits().value;ArrayList items = new ArrayList<>();SearchHit[] h = hits.getHits();for (int i = 0; i < value; i++) {Product item = JSON.parseObject(h[i].getSourceAsString(), Product.class);// 显示生成的高亮字符串Map highlightFields = h[i].getHighlightFields();//System.out.println(highlightFields);HighlightField HighlightField = highlightFields.get("title");Text[] fragments = HighlightField.fragments();//替换item.setTitle(fragments[0].toString());items.add(item);}System.out.println(items);}

2 集群相关知识

我们启动的一个Elasticsearch进程,我们称之为为一个Elasticsearch节点(Node),多个Elasticsearch节点,可以组成一个Elasticsearch集群,接下来,我们就来了解下Elasticsearch集群相关的知识

2.1 索引在集群中的分布

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I3WqSQRz-1669974393062)(Elastic Search2.assets/shard-1652621281595.png)]

无论是单机还是集群模式,ES中的索引数据都是以分片的形式存在的,一个索引的一个分片中只存储该索引中的一部分数据,即一个索引中的文档数据,被存储在其所属的若干个分片中,每个分片只存储索引部分数据。

对于一个索引所包含的所有分片,又被分成了两种:

  • 主分片:一个索引中的文档到底被被分成几部分来存储,主要看索引到底包含多少个主分片,有多少个主分片,索引数据数据就被分成几部分分别存储在主分片中。用户插入ES的文档数据都是首先存储到主分片的。
  • 副本分片:而副本分片主要是作为主分片的数据副本而存在,每个主分片都可以有对应的副本分片。

所以,在ES集群中,一个索引的多个分片数据,就保存在不同的ES服务器实例或者说不同的ES Node上,一个单机版的ES服务器,也可以看做是只包含一个ES Node的ES集群,所以一个ES Node可以包含索引的多个分片数据。

这里还有三点细节需要注意:

  • ES 不允许将一个主分片和它对应的副本分片存储在同一个ES Node中。这主要是为了防止,一个ES Node 宕机导致,某分片数据全部丢失(主分片和该主分片对应的副本分片的数据全部丢失)。这是因为如果每个主分片至少有一个副本分片,那么即使该主分片所在的ES Node宕机也没关系,ES会自动将其副本分片变为主分片,保证数据的正常访问。
  • 一个ES的索引由一个或多个主分片以及零个或多个副本分片构成,即ES不强制要求主分片一定有对应的副本分片
  • 在创建索引的时候就可以定义,索引的主分片数量,以及每个主分片对应的副本分片的数量,但是对于一个索引而言,一旦索引创建完毕,其主分片数量就不能在变了,只能修改其副本分片的数量

2.2 文档数据在ES集群中的存储和搜索

我们先来看看文档数据的存储:

  • 当存储或一篇文档的时候,ES首先根据文档ID的散列值,选择一个主分片,并将该文档发送到该主分片保存。
  • 然后,该文档被发送到主分片对应的所有的副本分片进行保存,这使得副本分片和主分片之间保持数据同步
  • 数据同步使得副本分片可以服务于搜索请求,并在原有主分片无法访问时自动升级为主分片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQu9heF9-1669974393063)(Elastic Search2.assets/文档的保存.png)]

在保存一篇文档的时候,我们讲解了如何决定一篇文档所在的分片的,这一过程我们称为文档路由。当ES散列文档ID时就会发生文档的路由,来决定文档应该索引到哪个分片中

再来看看在集群中搜索文档数据的过程,当在索引中搜索的时候,Elastic Search会在索引的所有分片中进行查找,这些分片可以是主分片,也可以是副本分片,原因是主分片和副本分片通常包含一样的文档。ES在索引的主分片和副本分片中进行搜索请求的负载均衡,使得副本分片对于搜索的性能和容错都有帮助。下面看看具体的搜索过程:

  • 搜索请求首先被一个ES Node接收,并将请求转发到一组包含所有数据的索引分片
  • 在选择转发请求的分片时,使用轮训算法选择可用分片(这里轮训的是某主分片和其对应的副本分片,并且对于索引中的每一个主分片与其对应的副本分片都会有这样的轮训选择过程),并将搜索请求转发到所有选中的分片,
  • 然后,ES将从这些接收到请求的分片收集搜索的结果,将其聚集到单一的回复,然后将回复返回给客户端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9vcsSEm-1669974393063)(Elastic Search2.assets/文档的搜索.png)]

2.3 脑裂问题

在一个ES集群中,包含多个ES Node,这多个ES Node又可以扮演不同的角色,实现不同的功能,主要有以下几种角色:

  • Matster Node:负责创建,删除索引,以及给节点分配分片等集群管理的共工作
  • Data Node: 负责存储文档数据,以及对文档数据的CRUD操作
  • Coordinating Node: 负责接收客户端的请求,并将请求分发到各个相关节点,并最终收集各节点返回的结果,整合为一个统一的结果,返回给客户端, 其实每个ES Node都隐式的扮演者协调节点的角色。

在这其中,Data Node和Coordinating Node都可以有多个,但是Matster Node作为管理集群的"大脑"。正常情况下,当主节点无法工作时,会从备选主节点中选举一个出来变成新主节点,原主节点回归后变成备选主节点

但有时因为网络抖动等原因,主节点没能及时响应,集群误以为主节点挂了,选举了一个新主节点,此时一个es集群中有了两个主节点,其他节点不知道该听谁的调度,结果将是灾难性的!这种类似一个人得了精神分裂症,就被称之为“脑裂”现象。

之所以产生脑裂问题的原因是主节点因为各种原因,在收到请求后未能及时响应,导致主节点未能及时响应的原因,一般主要有以下几点:

  • 网络抖动

    内网一般不会出现es集群的脑裂问题,可以监控内网流量状态。外网的网络出现问题的可能性大些

  • 节点负载

    如果主节点同时承担数据节点的工作,可能会因为工作负载大而导致对应的 ES 实例停止响。

  • 内存回收

    由于数据节点上es进程占用的内存较大,较大规模的内存回收操作也能造成es进程失去响应。

如何解决脑裂问题呢?

  • 不要把主节点同时设为数据节点(node.master和node.data不要同时设为true),node.master=true意味着该节点有竞选Master Node的资格,node.date=true,意味着该节点扮演数据节点的角色

  • 将节点响应超时(discovery.zen.ping_timeout)稍稍设置长一些(默认是3秒),避免误判。

  • 设置需要超过半数的备选节点同意,才能发生主节点重选,类似需要参议院半数以上通过,才能弹劾现任总统。(discovery.zen.minimum_master_nodes = 半数以上备选主节点数)。

相关内容

热门资讯

北京1号线福寿岭站今年有望启用 (来源:千龙网)石景山区·民生改善昨(13)日,记者从2026年石景山区“两会”新闻发布会上了解到,...
完善体教融合机制 激发校园运... 1月8日,北京景山学校排球队小学组队员黄浩洋(前)在专注练习垫球。 新华社记者 王丽莉 摄 ...
美国30年期国债发行中标收益率...   美国财政部发行220亿美元30年期国债,中标收益率4.825%,低于发行前交易水平4.833%。...
“最神秘的鸟”海南鳽现身内江 受伤的海南鳽获得救助。图据威远公安  “是派出所吗?我这儿发现一只鸟儿受伤了,看起来像是保护动物,你...
麦加芯彩新材料科技(上海)股份... 证券代码:603062 证券简称:麦加芯彩 公告编号:2026-004麦加芯彩新材料科技(上海)股...