袁庭新ES系列18节|Spring Data Elasticsearch高级

前言

这一章节袁老师将带领同学们来学习Spring Data Elasticsearch高级操作相关的内容。我们继续来探索SDE是如何将原始操作Elasticsearch的客户端API进行封装的,以及通过Spring Data Elasticsearch如何来操作ES。准备好了吗?我们继续来探索ES的内容。

一. 索引数据CRUD操作

SDE的索引数据CRUD操作并没有封装在ElasticsearchTemplate类中,封装在ElasticsearchRepository这个接口中。

在com.yx.respository包下自定义ProductRepository接口,并继承ElasticsearchRespository接口。

package com.yx.respository;
import com.yx.pojo.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface ProductRepository extends ElasticsearchRepository<Product, Long> {

}

1.创建索引数据

创建索引数据时,有单个创建和批量创建之分。

1.先来看单个创建。在SpringDataESTests类中定义addDocument()方法。

@Autowired
private ProductRepository productRepository;

@Test
public void addDocument() {
    Product product = new Product(1L, "小米手机Mix", "手机", "小米", 2899.00, "http://image.yx.com/12479122.jpg");
    
    // 添加索引数据
    productRepository.save(product);
}

2.再来看批量创建。在SpringDataESTests类中定义addDocuments()方法。

@Test
public void addDocuments() {
    // 准备文档数据
    List<Product> list = new ArrayList<>();

    // 添加数据
    list.add(new Product(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.yx.com/12479122.jpg"));
    list.add(new Product(3L, "华为META20", "手机", "华为", 4499.00, "http://image.yx.com/12479122.jpg"));
    list.add(new Product(4L, "小米Pro", "手机", "小米", 4299.00, "http://image.yx.com/12479122.jpg"));
    list.add(new Product(5L, "荣耀V20", "手机", "华为", 2799.00, "http://image.yx.com/12479122.jpg"));

    // 添加索引数据
    productRepository.saveAll(list);
}

2.查询索引数据

2.1.根据id查询数据

ElasticsearchRepository接口中封装了根据id查询的findById(ID var1)方法。

1.在SpringDataESTests类中定义findById()方法。

@Test
public void findById() {
    Optional<Product> optional = productRepository.findById(1L);
    
    Product defaultProduct = new Product();
    defaultProduct.setTitle("默认商品数据");
    
    // orElse(T other)方法:如果Optional对象中封装的泛型为null,则将orElse()方法的参数作为返回值
    Product product = optional.orElse(defaultProduct);
    
    System.err.println(product);
}

2.运行findById()方法,输出结果见下:

Product(id=1, title=小米手机Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg)

2.2.查询所有数据

ElasticsearchRepository接口中封装了查询所有数据的findAll()方法。

1.在SpringDataESTests类中定义findAll()方法。

@Test
public void findAll() {
    Iterable<Product> list = productRepository.findAll();
    
    list.forEach(System.err::println);
}

2.运行findAll()方法,输出结果见下:

Product(id=2, title=坚果手机R1, category=手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)
Product(id=4, title=小米Pro, category=手机, brand=小米, price=4299.0, images=http://image.yx.com/12479122.jpg)
Product(id=5, title=荣耀V20, category=手机, brand=华为, price=2799.0, images=http://image.yx.com/12479122.jpg)
Product(id=1, title=小米手机Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg)
Product(id=3, title=华为META20, category=手机, brand=华为, price=4499.0, images=http://image.yx.com/12479122.jpg)

3.自定义方法查询

3.1.存储库查询关键字

ElasticsearchRepository提供的查询方法有限,但是它却提供了非常强大的自定义查询功能。只要遵循Spring Data Elasticsearch提供的语法,我们可以任意定义方法声明。

关键字

示例

Elasticsearch查询字符串

And

findByNameAndPrice

{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Or

findByNameOrPrice

{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Is

findByName

{"bool" : {"must" : {"field" : {"name" : "?"}}}}

Not

findByNameNot

{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}

Betweend

findByPriceBetween

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

LessThanEqual

findByPriceLessThan

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

GreaterThanEqual

findByPriceGreaterThan

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Before

findByPriceBefore

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

After

findByPriceAfter

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Like

findByNameLike

{"bool" : {"must" : {"field" : {"name" : {"query" : "? *","analyze_wildcard" : true}}}}}

StartingWith

findByNameStartingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "? *","analyze_wildcard" : true}}}}}

EndingWith

findByNameEndingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}

Contains/Containing

findByNameContaining

{"bool" : {"must" : {"field" : {"name" : {"query" : "","analyze_wildcard" : true}}}}}

In

findByNameIn(Collection<String>names)

{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}

NotIn

findByNameNotIn(Collection<String>names)

{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}

Near

findByStoreNear

Not Supported Yet !

True

findByAvailableTrue

{"bool" : {"must" : {"field" : {"available" : true}}}}

False

findByAvailableFalse

{"bool" : {"must" : {"field" : {"available" : false}}}}

OrderBy

findByAvailableTrueOrderByNameDesc

{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

3.2.自定义方法查询案例

1.在ProductRepository接口中定义根据商品的价格区间查询商品数据的findByPriceBetween()方法。

/**
* 根据商品的价格区间查询商品数据
* @param from 开始价格
* @param to 结束价格
* @return 符合条件的商品数据
*/
List<Product> findByPriceBetween(Double from, Double to);

2.无需写实现,SDE会自动帮我们实现该方法,我们只需要用即可。在SpringDataESTests类中定义findByPriceBetween()方法。

@Test
public void findByPriceBetween() {
    List<Product> products = productRepository.findByPriceBetween(3000d, 5000d);
    products.forEach(System.err::println);
}

3.运行findByPriceBetween()方法,输出结果见下:

Product(id=2, title=坚果手机R1, category= 手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)
Product(id=4, title=小米Pro, category= 手机, brand=小米, price=4299.0, images=http://image.yx.com/12479122.jpg)
Product(id=3, title=华为META20, category= 手机, brand=华为, price=4499.0, images=http://image.yx.com/12479122.jpg)

二. 原生查询

1.原生查询介绍

如果觉得上述接口依然不符合你的需求,SDE也支持原生查询。这个时候还是使用ElasticsearchTemplate,而查询条件的构建是通过一个名为NativeSearchQueryBuilder的类来完成的,不过这个类的底层还是使用的原生API中的QueryBuilders、AggregationBuilders、HighlightBuilders等工具来实现的。

2.原生查询案例

需求描述:

  • 查询title中包含“手机”的商品;
  • 且以价格升序进行排序;
  • 进行分页查询:每页展示2条数据,查询第1页;
  • 最后对查询结果进行聚合分析:获取品牌及个数。

1.在SpringDataESTests类中定义nativeQuery()方法。

@Test
public void nativeQuery() {
    /* 1 查询结果 */
    
    // 1.1 原生查询构建器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    
    // 1.2 source过滤
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[0]));
    
    // 1.3 搜索条件
    queryBuilder.withQuery(QueryBuilders.matchQuery("title", "手机"));
    
    // 1.4 分页及排序条件
    queryBuilder.withPageable(PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "price")));
    
    // 1.5 高亮显示
    // TODO
    
    // 1.6 聚合
    queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brand"));
    
    // 1.7 构建查询条件,并且查询
    AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class);

    /* 2 解析结果 */
    
    // 2.1 分页结果
    long total = result.getTotalElements();
    int totalPages = result.getTotalPages();
    List<Product> list = result.getContent();
    System.out.println("总条数:" + total);
    System.out.println("总页数:" + totalPages);
    System.out.println("数据:" + list);
    
    // 2.2 聚合结果
    Aggregations aggregations = result.getAggregations();
    // 导包org.elasticsearch.search.aggregations.bucket.terms.Terms
    Terms terms = aggregations.get("brandAgg");
    terms.getBuckets().forEach(b -> {
        System.out.println("品牌:" + b.getKeyAsString());
        System.out.println("count:" + b.getDocCount());
    });
}

注意:上述查询不支持高亮结果。

2.运行nativeQuery()方法,输出结果见下:

总条数:2
总页数:1
数据:[Product(id=1, title=小米手机Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg), Product(id=2, title=坚果手机R1, category=手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)]
品牌:小米
count:1
品牌:锤子
count:1

三. 高亮显示

1.高亮显示需求

需求描述:查询title中包含“小米手机”的商品,并将title中对应的分词通过<span style='color:red'></span>标签进行高亮修饰。

2.高亮显示实现

1.在com.yx.mapper包下自定义搜索结果映射ProductSearchResultMapper类。

package com.yx.mapper;
import com.google.gson.Gson;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/** 自定义查询结果映射,用于处理高亮显示 */
public class ProductSearchResultMapper implements SearchResultMapper {
    /**
	 * 完成查询结果映射。将_source取出,然后放入高亮的数据
 	 */
    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
        // 记录总条数
        long totalHits = searchResponse.getHits().getTotalHits();
        
        // 记录列表(泛型) - 构建Aggregate使用
        List<T> list = new ArrayList<>();
        
        // 获取搜索结果(真正的的记录)
        SearchHits hits = searchResponse.getHits();
        
        for (SearchHit hit : hits) {
            if (hits.getHits().length <= 0) {
                return null;
            }
            
            // 将原本的JSON对象转换成Map对象
            Map<String, Object> map = hit.getSourceAsMap();
            
            // 获取高亮的字段Map
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            
            for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
                // 获取高亮的Key
                String key = highlightField.getKey();
                // 获取高亮的Value
                HighlightField value = highlightField.getValue();
                // 实际fragments[0]就是高亮的结果,无需遍历拼接
                Text[] fragments = value.getFragments();
                // 因为高亮的字段必然存在于Map中,就是key值
                map.put(key, fragments[0].toString());
            }
            
            // 把Map转换成对象
            Gson gson = new Gson();
            T item = gson.fromJson(gson.toJson(map), aClass);
            list.add(item);
        }
        
        // 返回的是带分页的结果
        return new AggregatedPageImpl<>(list, pageable, totalHits);
    }
}

2.高亮实现。重构nativeQuery()方法,使用自定义查询结果映射,来实现高亮显示。

@Test
public void nativeQuery() {
    /* 1 查询结果 */
    
    // 1.1 原生查询构建器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    
    // 1.2 source过滤
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[0]));
    
    // 1.3 搜索条件
    queryBuilder.withQuery(QueryBuilders.matchQuery("title", "手机"));
    
    // 1.4 分页及排序条件
    queryBuilder.withPageable(PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "price")));
    
    // 1.5 高亮显示
    HighlightBuilder.Field field = new HighlightBuilder.Field("title");
    field.preTags("<span style='color:red'>");
    field.postTags("</span>");
    queryBuilder.withHighlightFields(field);
    
    // 1.6 聚合
    queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brand"));
    
    // 1.7 构建查询条件,并且查询
    // AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class);
    AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class, new ProductSearchResultMapper());

    /* 2 解析结果 */
    
    // 2.1 分页结果
    long total = result.getTotalElements();
    int totalPages = result.getTotalPages();
    List<Product> list = result.getContent();
    System.out.println("总条数:" + total);
    System.out.println("总页数:" + totalPages);
    System.out.println("数据:" + list);
    
    // 2.2 聚合结果
    /*
    Aggregations aggregations = result.getAggregations();
    // 导包org.elasticsearch.search.aggregations.bucket.terms.Terms
    Terms terms = aggregations.get("brandAgg");
    terms.getBuckets().forEach(b -> {
    System.out.println("品牌:" + b.getKeyAsString());
    System.out.println("count:" + b.getDocCount());
    });
	*/
}

3.运行nativeQuery()测试方法后,输出结果见下。

总条数:2
总页数:1
数据:[Product(id=1, title=小米<span style='color:red'>手机</span>Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg), Product(id=2, title=坚果<span style='color:red'>手机</span>R1, category=手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)]

四. 结语

Spring Data Elasticsearch基于spring data API大大简化了Elasticsearch的操作,从而简化开发人员的代码,提高开发效率。然后我们给大家介绍了使用Spring Data对Elasticsearch进行了增、删、改、查操作。

同学们,关于Elasticsearch的所有内容袁老师就给大家介绍到这里。还有很多前沿技术等着大家去探索,“路漫漫其修远昔,吾将上下而求索”。最后,祝愿各位小伙伴未来学业有成,一切如意!

今天的内容就分享到这里吧。关注「袁庭新」,干货天天都不断!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/587065.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio 调试:快速入门指南

作为一名Android应用开发人员&#xff0c;调试是你不可或缺的技能之一。通过调试&#xff0c;你可以定位和解决各种问题&#xff0c;包括崩溃、性能问题、UI错误等。在本文中&#xff0c;我们将分享一些实用的Android调试技巧&#xff0c;帮助你提高应用开发效率。 Android St…

餐后血糖波动?学会在米饭里加两物

米饭里加两物&#xff0c;帮你平稳餐后血糖&#xff0c;餐后血糖稳稳的&#xff0c;别让你碗里的米饭太单调&#xff0c;搭着吃对血糖好。今天呢我教大家一招&#xff0c;在蒸米饭的时候&#xff0c;加上两种食材&#xff0c;能够改善餐后血糖。 第一就是在煮米饭的时候加点糙米…

STM32 F103C8T6学习笔记17:类IIC通信—MLX90614红外非接触温度计

今日学习配置MLX90614红外非接触温度计 与 STM32 F103C8T6 单片机的通信 文章提供测试代码讲解、完整工程下载、测试效果图 本文需要用到的大概基础知识&#xff1a;1.3寸OLED配置通信显示、IIC通信、 定时器配置使用 这里就只贴出我的 OLED驱动方面的网址链接了&#xff1a…

【热闻速递】Google 裁撤 Python研发团队

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 【&#x1f525;热闻速递】Google 裁撤 Python研发团队引入研究结论 【&#x1f5…

配置及使用OpenCV(Python)

python配置OpenCV相对于c的配置方法容易的多&#xff0c;但建议在Anaconda中的Python虚拟环境中使用&#xff0c;这样更方便进行包管理和环境管理&#xff1a; 先激活Anaconda的python虚拟环境&#xff1a; conda activate GGBoy 随后下载 opencv 包&#xff1a; conda ins…

数据结构——树概念以及结构

首先我们来复习一下顺序表和链表的优缺点。 顺序表缺点&#xff1a; 1.中间或者头部插入、删除数据需要挪动覆盖&#xff0c;效率低 2.空间不够只能扩容&#xff0c;扩容有消耗 3.倍数扩容&#xff0c;空间用不完&#xff0c;存在浪费空间 顺序表优点&#xff1a; 1.可以…

低代码工业组态数字孪生平台

2024 两会热词「新质生产力」凭借其主要特征——高科技、高效能及高质量&#xff0c;引发各界关注。在探索构建新质生产力的重要议题中&#xff0c;数据要素被视为土地、劳动力、资本和技术之后的第五大生产要素。数据要素赋能新质生产力发展主要体现为&#xff1a;生产力由生产…

电商日志项目(一)

电商日志项目 一、项目体系架构设计1. 项目系统架构2. 项目数据流程二、环境搭建1. NginxLog文件服务1.1. 上传,解压1.2. 编译安装1.3. 启动验证2. Flume-ng2.1. 上传解压2.2. 修改配置文件2.3. 修改环境变量2.4. 验证3. Sqoop3.1. 上传解压3.2. 配置环境变量3.3. 修改配置文件…

【19】JAVASE-多线程专题【从零开始学JAVA】

Java零基础系列课程-JavaSE基础篇 Lecture&#xff1a;波哥 Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。…

[高质量]2024五一数学建模A题保奖思路+代码(后续会更新)

你的点赞收藏是我继续更新的最大动力&#xff0c;可点击文末卡片获取更多资料 你是否在寻找数学建模比赛的突破点&#xff1f; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024 年华东杯&#xff08;A题&#xff09;的全面解析包。这个解决方案包不仅包括完整的代…

2024年十款开源测试开发工具推荐(自动化、性能、混沌测试、造数据、流量复制)

今天为大家奉献一篇测试开发工具集锦干货。在本篇文章中&#xff0c;将给大家推荐10款日常工作中经常用到的测试开发工具神器&#xff0c;涵盖了自动化测试、性能压测、流量复制、混沌测试、造数据等。 1、AutoMeter-API 自动化测试平台 AutoMeter 是一款针对分布式服务&…

DigitalOcean 托管 Kafka 新增横向扩展功能

自2023年9月推出以来&#xff0c;DigitalOcean托管的Kafka已经使初创公司、不断增长的数字业务以及独立软件供应商(ISV)能够改善实时数据处理和分析&#xff0c;从而做出更具洞察力的决策。在新的一年里&#xff0c;我们很高兴地宣布DigitalOcean托管Kafka的横向扩展功能&#…

写文献综述常用的几种深度神经网络模型!

写文献综述常用的几种深度神经网络模型 卷积神经网络&#xff08;CNN&#xff09; 解释说明&#xff1a;专门用于处理图像和图像数据的深度学习模型。它通过卷积层、池化层等操作提取图像特征。应用&#xff1a;图像分类、目标检测、人脸识别等。未来改进&#xff1a;进一步提…

数据结构篇2—《单链表(不带头单向不循环链表)》

文章目录 &#x1f6a9;前言1、单链表的内涵(1) 逻辑结构(2) 物理结构 2、链表的分类3、单链表的具体实现(1) 框架结构(2) SingleLinkList.h头文件的实现(3)SingleLinkList.c源文件的实现①SLTPushBack()尾插函数②SLTPushFront()头插函数③SLTPopBack()尾删函数④SLTPopFront(…

高效管理—影视管理系统_后台源码+APP源码+电影数据

高效管理—影视管理系统 产品概述产品展示演示地址产品功能产品优势产品服务源码领取下期更新预报 产品概述 本产品是一个功能强大且易于使用的影视资源管理工具&#xff0c;它提供了一个集中管理和组织电影、电视剧、纪录片等影视作品的平台&#xff0c;帮助用户高效地管理和…

easyExcel - 带图片导出

目录 前言一、情景介绍二、问题分析三、代码实现1. 单图片导出2. 多图片导出3. 多图片导出&#xff08;优化&#xff09; 前言 Java-easyExcel入门教程&#xff1a;https://blog.csdn.net/xhmico/article/details/134714025 之前有介绍过如何使用 easyExcel&#xff0c;以及写…

中间件之异步通讯组件RabbitMQ入门

一、概述 微服务一旦拆分&#xff0c;必然涉及到服务之间的相互调用&#xff0c;目前我们服务之间调用采用的都是基于OpenFeign的调用。这种调用中&#xff0c;调用者发起请求后需要等待服务提供者执行业务返回结果后&#xff0c;才能继续执行后面的业务。也就是说调用者在调用…

【星海随笔】windows 上跑MySQL

step one 使用 WSL 在 Windows 上安装 Linux wsl官方文档 在管理员模式下打开 PowerShell windows上安装wsl wsl --install查看哪些是可用的 wsl --list --onlineC:\Windows\System32\drivers\hosts docker-desktop下载官网&#xff1a;Install Docker Desktop on Windows …

[ log日志画图]分割模型训练结束生成相关日志运用代码画图

文章目录 [ log日志画图]分割模型训练结束生成相关日志运用代码画图我的log文件&#xff1a;画图&#xff1a;1.loss1.1 loss是干嘛的1.2 代码1.3 生成图 2.DICE.IOU2.1 DICE,IOU是干嘛的(常规介绍)2.2 代码2.3 生成图小白tip [ log日志画图]分割模型训练结束生成相关日志运用代…

ROS1快速入门学习笔记 - 12ROS中的坐标管理系统

目录 一、机器人作中的坐标变换 二、海龟案例 一、机器人作中的坐标变换 TF功能包能干什么&#xff1f; 五秒钟之前&#xff0c;机器人头部坐标系相对于全局坐标系的关系是什么样子的&#xff1f;机器人夹取的物体i相对于机器人中心坐标系的位置在哪里&#xff1f;机器人中心…