云商城-海量数据搜索实现
第4章 海量数据搜索实现
课程目标
1、商品搜索流程讲解
1)搜索引擎技术分析
2)搜索引擎流程实现
2、商品关键词搜索功能实现
1)工程搭建
2)索引实时导入
3)关键词搜索
3、商品统计条件搜索
1)分类搜索实现
2)品牌搜索实现
4、商品搜索条件筛选实现
1)属性动态域分析
2)属性动态域搜索
3)价格区间搜索
5、商品搜索排序实现
1)排序分析
2)多种排序实现
6、商品搜索高亮实现
1)高亮展示分析
2)高亮搜索实现
环境说明:
1、es-head已装好,端口9100
2、ElasticSearch已装好
3、安装文档:参考资料->ES安装文档
1 商品搜索环境搭建
商品搜索,包含了数据实时同步和搜索过程,这2个过程必须清楚才能做出更符合工作需要的搜索。
1.1 搜索分析

数据同步流程如下:
1、数据通过程序将Sku添加到数据库
2、通过Canal实现实时将数据同步到ES搜索引擎
3、用户可以在前台使用不同条件进行搜索,关键词、分类、价格区间、动态属性

1.2 工程搭建
1.2.1 api工程搭建
1)工程搭建
创建搜索工程的api工程mall-search-api,坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-search-api</artifactId>
pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-api</artifactId>
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-search-api</artifactId>
<dependencies>
<!--ElasticSearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
</project>
2)映射配置

我们需要将数据库数据查询出来,再存入ES中,但中间需要有一个和ES索引库对应的JavaBean,为了不影响原来程序对象,所以会创建一个新的JavaBean对象com.gupaoedu.vip.mall.search.model.SkuEs,代码如下:
@Data
@Document(indexName = "shopsearch",type = "skues")
public class SkuEs {
@Id
private String id;
private String name;
private Integer price;
private Integer num;
private String image;
private String images;
private Date createTime;
private Date updateTime;
private String spuId;
private Integer categoryId;
//Keyword:不分词
@Field(type=FieldType.Keyword)
private String categoryName;
private Integer brandId;
@Field(type=FieldType.Keyword)
private String brandName;
private String skuAttribute;
private Integer status;
//属性映射
private Map<String,String> attrMap;
}
1.2.2 搜索工程搭建
搭建搜索工程,坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-search-service</artifactId>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-service</artifactId>
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-search-service</artifactId>
<description>
搜索服务
</description>
<dependencies>
<!--mall-search-api-->
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-search-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
bootstrap.yml:
server:
port: 8084
spring:
application:
name: mall-search
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.100.130:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.100.130:8848
#Elasticsearch服务配置
elasticsearch:
rest:
uris: http://192.168.100.130:9200
#日志配置
logging:
pattern:
console: "%msg%n"
启动类:com.gupaoedu.vip.mall.MallSearchApplication
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MallSearchApplication {
public static void main(String[] args) {
SpringApplication.run(MallSearchApplication.class,args);
}
}
1.3 索引实时更新
当数据库中sku数据发生变更的时候,我们可以通过Canal微服务调用当前搜索微服务实现数据实时更新。
1.3.1 更新操作实现
1)Mapper
创建Dao接口需要继承ElasticsearchRepository<T,ID>接口,同时需要在启动类中添加扫描注解。
创建com.gupaoedu.vip.mall.search.mapper.SkuSearchMapper:
public interface SkuSearchMapper extends ElasticsearchRepository<SkuEs,String> {
}
在启动类上添加EnableElasticsearchRepositories注解:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableElasticsearchRepositories(basePackages = {"com.gupaoedu.vip.mall.search.mapper"})
public class MallSearchApplication {
public static void main(String[] args) {
SpringApplication.run(MallSearchApplication.class,args);
}
}
2)Service
创建接口:com.gupaoedu.vip.mall.search.service.SkuSearchService,创建添加和删除方法:
public interface SkuSearchService {
//添加索引
void add(SkuEs skuEs);
//删除索引
void del(String id);
}
创建实现类:com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl
@Service
public class SkuSearchServiceImpl implements SkuSearchService {
@Autowired
private SkuSearchMapper skuSearchMapper;
/***
* 单个导入ES
* @param skuEs
*/
@Override
public void add(SkuEs skuEs) {
//属性转换
String skuAttribute = skuEs.getSkuAttribute();
if(!StringUtils.isEmpty(skuAttribute)){
skuEs.setAttrMap(JSON.parseObject(skuAttribute,Map.class));
}
skuSearchMapper.save(skuEs);
}
/***
* 根据ID删除
* @param id
*/
@Override
public void del(String id) {
skuSearchMapper.deleteById(id);
}
}
3)Controller
创建com.gupaoedu.vip.mall.search.controller.SkuSearchController
@RestController
@RequestMapping(value = "/search")
public class SkuSearchController {
@Autowired
private SkuSearchService skuSearchService;
/****
* 单个商品导入
*/
@PostMapping(value = "/add")
public RespResult add(@RequestBody SkuEs skuEs){
skuSearchService.add(skuEs);
return RespResult.ok();
}
/***
* 根据ID删除
* @param id
* @return
*/
@DeleteMapping(value = "/del/{id}")
public RespResult del(@PathVariable("id")String id){
skuSearchService.del(id);
return RespResult.ok();
}
}
4)Feign
在mall-search-api中创建com.gupaoedu.vip.mall.search.feign.SkuSearchFeign
@FeignClient(value = "mall-search")
public interface SkuSearchFeign {
/****
* 单个导入
* @param skuEs
* @return
*/
@PostMapping(value = "/search/add")
RespResult add(@RequestBody SkuEs skuEs);
/***
* 根据ID删除
* @param id
* @return
*/
@DeleteMapping(value = "/search/del/{id}")
RespResult del(@PathVariable("id")String id);
}
1.3.2 实时更新实现
1.3.2.1 映射配置
实时更新,我们需要在Canal微服务中调用上面2个接口,首先我们要先将数据库中通过Canal推送的数据转成指定的JavaBean,需要用到JPA注解配置映射。
在mall-api中引入JPA:
<!--JPA-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
在mall-goods-api中的Sku中配置映射如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "sku")
@Table
public class Sku implements Serializable{
@TableId(type = IdType.ASSIGN_ID)
private String id;
private String name;
private Integer price;
private Integer num;
private String image;
private String images;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
@Column(name = "spu_id")
private String spuId;
@Column(name = "category_id")
private Integer categoryId;
@Column(name = "category_name")
private String categoryName;
@Column(name = "brand_id")
private Integer brandId;
@Column(name = "brand_name")
private String brandName;
@Column(name = "sku_attribute")
private String skuAttribute;
private Integer status;
}
1.3.2.2 监听更新
监听更新需要调用search的接口,所以需要引入依赖包,在mall-canal-service添加如下依赖:
<!--searchapi-->
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-search-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在mall-canal-service的启动类上添加Feign注解@EnableFeignClients配置:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients(basePackages = {"com.gupaoedu.vip.mall.goods.feign","com.gupaoedu.vip.mall.search.feign"})
public class MallCanalApplication {
public static void main(String[] args) {
SpringApplication.run(MallCanalApplication.class,args);
}
}
在mall-canal-service中创建com.gupaoedu.vip.canal.listener.SkuHandler
@CanalTable(value = "sku")
@Component
public class SkuHandler implements EntryHandler<Sku>{
@Autowired
private SkuSearchFeign skuSearchFeign;
/***
* 增加产品
* @param sku
*/
@Override
public void insert(Sku sku) {
if(sku.getStatus().intValue()==1){
//导入索引
skuSearchFeign.add(JSON.parseObject(JSON.toJSONString(sku), SkuEs.class));
}
}
/***
* 修改
* @param before
* @param after
*/
@Override
public void update(Sku before, Sku after) {
if(after.getStatus().intValue()==2){
//导入索引
skuSearchFeign.del(after.getId());
}else{
skuSearchFeign.add(JSON.parseObject(JSON.toJSONString(after), SkuEs.class));
}
}
/***
*
* @param sku
*/
@Override
public void delete(Sku sku) {
skuSearchFeign.del(sku.getId());
}
}
修改数据库数据,同时打开http://192.168.100.130:9100/,效果如下:

2 商品关键词搜索
2.1 搜索分析

用户输入关键词keywords后,将keywords关键词一起传入后台,需要根据商品名字进行搜索。以后也有可能根据别的条件查询,所以传入后台的数据可以用Map接收,响应页面的数据包含列表、分页等信息,可以用Map封装。
2.2 搜索实现
1)Service
修改com.gupaoedu.vip.mall.search.service.SkuSearchService添加搜索方法:
/***
* 商品搜索
* @param searchMap
* @return
*/
Map<String,Object> search(Map<String, Object> searchMap);
修改com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl添加搜索实现:
/***
* 商品搜索
* @param searchMap
* @return
*/
@Override
public Map<String, Object> search(Map<String, Object> searchMap) {
//条件封装
NativeSearchQueryBuilder queryBuilder = queryBuilder(searchMap);
//执行搜索
Page<SkuEs> result = skuSearchMapper.search(queryBuilder.build());
//结果集
List<SkuEs> list = result.getContent();
//中记录数
long totalElements = result.getTotalElements();
Map<String,Object> resultMap = new HashMap<String,Object>();
resultMap.put("list",list);
resultMap.put("totalElements",totalElements);
return resultMap;
}
/***
* 搜索条件组装
*/
public NativeSearchQueryBuilder queryBuilder(Map<String,Object> searchMap){
//QueryBuilder构建
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//条件判断
if(searchMap!=null && searchMap.size()>0){
//关键词
Object keywords =searchMap.get("keywords");
if(!StringUtils.isEmpty(keywords)){
queryBuilder.withQuery(QueryBuilders.termQuery("name",keywords.toString()));
}
}
return queryBuilder;
}
2)Controller
修改com.gupaoedu.vip.mall.search.controller.SkuSearchController添加搜控制方法:
/****
* 商品搜索
*/
@GetMapping
public RespResult<Map<String,Object>> search(@RequestParam Map<String,Object> searchMap){
Map<String,Object> result = skuSearchService.search(searchMap);
return RespResult.ok(result);
}
测试结果如下:http://localhost:8084/search?keywords=华为
{
"code": 20000,
"data": {
"list": [
{
"attrMap": {
"学习费用": "2万",
"就业薪资": "10K起"
},
"brandId": 11,
"brandName": "华为",
"categoryId": 11159,
"categoryName": "软件研发",
"createTime": 1603184062000,
"id": "1318596430398562305",
"image": "...",
"images": "...",
"name": "华为Mate40 Pro 128G",
"num": 2,
"price": 3,
"skuAttribute": "{\"就业薪资\":\"10K起\",\"学习费用\":\"2万\"}",
"spuId": "1318596430293704706",
"status": 1,
"updateTime": 1603184062000
}
],
"totalElements": 3
},
"message": "操作成功"
}
3 商品搜索条件回显
3.1 条件回显分析

我们每次执行搜索的时候,页面会显示不同搜索条件,例如:品牌、分类、属性,这些搜索条件都不是固定的,其实他们是没执行搜索的时候,符合搜索条件的商品所有品牌和所有分类,以及所有属性,把他们查询出来,然后页面显示。但是这些条件都没有重复的,也就是说要去重,去重一般采用分组查询即可,所以我们要想动态获取这样的搜索条件,我们需要在后台进行分组查询。
3.2 品牌、分类条件查询
1)分组设置
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl中添加方法
/***
* 分组搜索
*/
public void group(NativeSearchQueryBuilder builder,Map<String,Object> searchMap){
//前端没有传入分类参数的时候查询分类集合作为搜索条件
if(StringUtils.isEmpty(searchMap.get("category"))){
//分类分组
builder.addAggregation(AggregationBuilders
.terms("categoryList") //查询的数据对应别名
.field("categoryName") //根据categoryName分组
.size(100)); //分组查询100条
}
//前端没有传入品牌参数的时候查询品牌集合作为搜索条件
if(StringUtils.isEmpty(searchMap.get("brand"))){
//品牌分组
builder.addAggregation(AggregationBuilders
.terms("brandList") //查询的数据对应别名
.field("brandName") //根据brandName分组
.size(100)); //分组查询100条
}
}
2)分组结果解析
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl中添加方法
/****
* 取出所有分组数据
* @param aggregations
* @return
*/
public Map<String,Object> parseGroup(Aggregations aggregations) {
//所有分组数据
Map<String,Object> groups = new HashMap<String,Object>();
for (Aggregation aggregation : aggregations) {
//强转成ParsedStringTerms
ParsedStringTerms pst= (ParsedStringTerms) aggregation;
//定义一个集合存储
List<String> values = new ArrayList<String>();
for (Terms.Bucket bucket : pst.getBuckets()) {
//单个对象值
values.add(bucket.getKeyAsString());
}
//存入到Map中
groups.put(pst.getName(),values);
}
return groups;
}
3)调用实现分组查询
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl的search方法中添加上面2个方法的调用:

请求测试:http://localhost:8084/search,结果如下:
{
"code": 20000,
"data": {
"categoryList": [
"软件研发",
"手机"
],
"brandList": [
"华为",
"中兴"
],
"list": [
{
"attrMap": {
"学习费用": "2万",
"就业薪资": "10K起"
},
"brandId": 11,
"brandName": "华为",
"categoryId": 11159,
"categoryName": "软件研发",
"createTime": 1603183717000,
"id": "1318594982227025922",
"image": "...",
"images": "...",
"name": "华为Mate40 Pro 32G",
"num": 44,
"price": 3,
"skuAttribute": "{\"就业薪资\":\"10K起\",\"学习费用\":\"2万\"}",
"spuId": "1318594982147334146",
"status": 1,
"updateTime": 1603183717000
}
],
"totalElements": 11
},
"message": "操作成功"
}
3.3 属性回显
3.3.1 属性条件分析

属性条件其实就是当前搜索的所有商品属性信息,所以我们可以把所有属性信息全部查询出来,然后把属性名作为key,属性值用集合存起来,就是我们页面要的属性条件了。
3.3.2 属性条件查询
1)属性分组查询
修改com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl的group方法,添加分组查询,代码如下:

上图代码如下:
//属性分组查询
builder.addAggregation(AggregationBuilders
.terms("attrmaps") //查询的数据对应别名
.field("skuAttribute") //根据brandName分组
.size(100000)); //分组查询100000条
2)属性数据分析

上图代码的思路如下:
1、将所有属性信息传过来,并取出存到groupList中
2、循环groupList
3、把所有相同属性名的结果集合收集到一起
4、收集完成后,替换原来groupMap中的属性数据
完整代码如下:
/****
* 属性解析
*/
public void attrParse(Map<String,Object> groupMap){
//获取属性分组数据
Object attrgroup = groupMap.get("attrmaps");
if(attrgroup!=null){
//强转成List
List<String> groupList = (List<String>) attrgroup;
//所有属性名拥有的属性集合
Map<String,Set<String>> allMaps = new HashMap<String,Set<String>>();
for (String attr : groupList) {
//将当前attr解析成Map
Map<String,String> attrMap = JSON.parseObject(attr,Map.class);
for (Map.Entry<String, String> entry : attrMap.entrySet()) {
//判断allMaps中是否存在当前attr的属性,不存在则添加一个
Set<String> values = allMaps.get(entry.getKey());
if(values==null){
values=new HashSet<String>();
}
//存在,则取出来,再添加当前值
values.add(entry.getValue());
//将修改后的值添加到allMaps中
allMaps.put(entry.getKey(),values);
}
}
//重置groupMap的值
groupMap.put("attrmaps",allMaps);
}
}
3)执行调用
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#search中执行调用,如下代码:

4 商品搜索条件筛选实现
4.1 条件搜索分析

用户在前端执行条件搜索的时候,有可能会选择分类、品牌、价格、属性,每次选择条件传入后台,后台按照指定参数进行条件查询,我们这里制定一个传参数的规则:
1、分类参数:category
2、品牌参数:brand
3、价格参数:price
4、属性参数:attr_属性名:属性值
5、分页参数:page
4.2 条件筛选查询
4.2.1 分类/品牌/价格查询
分别获取category,brand,price的值,并根据这三个只分别实现分类过滤、品牌过滤、价格过滤,其中价格过滤传入的数据以-分割,实现代码如下:
修改com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder添加代码如下图红色部分:

完整代码如下:
/***
* 搜索条件组装
*/
public NativeSearchQueryBuilder queryBuilder(Map<String,Object> searchMap){
//QueryBuilder构建
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//多条件组合查询对象
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//条件判断
if(searchMap!=null && searchMap.size()>0){
//关键词
Object keywords =searchMap.get("keywords");
if(!StringUtils.isEmpty(keywords)){
boolQuery.must(QueryBuilders.termQuery("name",keywords.toString()));
}
//分类
Object category =searchMap.get("category");
if(!StringUtils.isEmpty(category)){
boolQuery.must(QueryBuilders.termQuery("categoryName",category.toString()));
}
//品牌
Object brand =searchMap.get("brand");
if(!StringUtils.isEmpty(brand)){
boolQuery.must(QueryBuilders.termQuery("brandName",brand.toString()));
}
//价格
Object price =searchMap.get("price");
if(!StringUtils.isEmpty(price)){
//去掉元和以上,并按-分割
String[] prices = price.toString()
.replace("元","")
.replace("以上","")
.split("-");
//price>x
boolQuery.must(QueryBuilders.rangeQuery("price").gt(Integer.valueOf(prices[0])));
//price<=y
if(prices.length==2){
boolQuery.must(QueryBuilders.rangeQuery("price").lte(Integer.valueOf(prices[1])));
}
}
}
return queryBuilder.withQuery(boolQuery);
}
4.2.2 分页查询
编写分页实现,先获取当前页,再在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder调用该方法。
1)获取分页方法
/***
* 当前页
*/
public int currentPage(Map<String,Object> searchMap){
try {
Object currentPage = searchMap.get("page");
return Integer.valueOf(currentPage.toString())>0? Integer.valueOf(currentPage.toString())-1:0;
} catch (NumberFormatException e) {
return 0;
}
}
2)分页调用

4.2.3 属性查询
属性查询,每次传到后台的参数都是以attr_开始,我们可以遍历传过来的参数searchMap,判断是否是以attr_开始的参数,如果是,则查询属性。
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder中添加如下代码 即可:

上图代码如下:
//属性查询
for (Map.Entry<String, Object> entry : searchMap.entrySet()) {
if(entry.getKey().startsWith("attr_")){
//获取结果
String key = entry.getKey().replaceFirst("attr_","");
String value = entry.getValue().toString();
//执行查询
boolQuery.must(QueryBuilders.termQuery("attrMap."+key+".keyword",value));
}
}
5 商品搜索排序实现

5.1 排序搜索分析
排序搜索有多种排序方式,我们可以把排序升序、降序当做一个参数,把排序的域当做一个参数,无论是哪种排序方式,只需要把这两个参数传到后端服务即可。我们定义一下传参数规则:
1、排序域:sfield
2、排序方式:sm
例如:根据价格升序
sfield=price
sm=ASC
例如:新品排序
sfield=updateTime
sm=DESC
5.2 排序实现
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder中添加如下daima 即可:
//排序
Object sfield = searchMap.get("sfield");
Object sm = searchMap.get("sm");
if(!StringUtils.isEmpty(sfield) && !StringUtils.isEmpty(sm)){
queryBuilder.withSort(
SortBuilders.fieldSort(sfield.toString()) //排序域
.order(SortOrder.valueOf(sm.toString()))); //排序方式
}
6 商品搜索高亮实现
6.1 高亮原理分析
高亮是指搜索商品的时候,商品列表中如何和你搜索的关键词相同,那么它会高亮展示,也就是变色展示,如下京东搜索,其实就是给关键词增加了样式,所以是红色,ES搜索引擎也是一样,也可以实现关键词高亮展示,原理和京东搜索高亮原理一样。

6.2 高亮实现
高亮搜索实现有2个步骤:
1、配置高亮域以及对应的样式
2、从结果集中取出高亮数据,并将非高亮数据换成高亮数据
1)高亮配置
在com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#search中添加如下高亮代码:
//高亮配置
HighlightBuilder.Field field = new HighlightBuilder.
Field("name"). //指定的高亮域
preTags("<span style=\"color:red\">"). //前缀
postTags("</span>"). //后缀
fragmentSize(100);
//添加高亮域
queryBuilder.withHighlightFields(field);
2)结果映射转换
创建一个结果映射转换对象com.gupaoedu.vip.mall.search.es.HighlightResultMapper,其实主要是将非高亮转换成高亮数据,代码如下:
public class HighlightResultMapper extends DefaultResultMapper{
/***
* 处理结果集
*/
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
//所有数据
for (SearchHit hit : response.getHits()) {
//当前单条数据
Map<String, Object> sourceMap = hit.getSourceAsMap();
//高亮数据
for (Map.Entry<String, HighlightField> entry : hit.getHighlightFields().entrySet()) {
String key = entry.getKey();
if (sourceMap.containsKey(key)) {
Text[] fragments = entry.getValue().getFragments();
sourceMap.put(key, transTextArrayToString(fragments));
}
}
hit.sourceRef(new ByteBufferReference(ByteBuffer.wrap(JSONObject.toJSONString(sourceMap).getBytes())));
}
return super.mapResults(response, clazz, pageable);
}
/***
* 拼接数据碎片
*/
private String transTextArrayToString(Text[] fragments) {
if (null == fragments) {
return "";
}
StringBuffer buffer = new StringBuffer();
for (Text fragment : fragments) {
buffer.append(fragment.string());
}
return buffer.toString();
}
}
注入对象ElasticsearchRestTemplate:
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
将之前的搜索换成用elasticsearchRestTemplate来实现搜索:

测试关键词搜索,效果如下:
{
"code": 20000,
"data": {
"attrmaps": {
"学习费用": [
"2万"
],
"就业薪资": [
"10K起"
]
},
"categoryList": [
"软件研发"
],
"brandList": [
"华为"
],
"list": [
{
"attrMap": {
"学习费用": "2万",
"就业薪资": "10K起"
},
"brandId": 11,
"brandName": "华为",
"categoryId": 11159,
"categoryName": "软件研发",
"createTime": 1603184062000,
"id": "1318596430398562305",
"image": "....",
"images": "....",
"name": "<span style=\"color:red\">华为</span>Mate40 Pro 128G",
"num": 122,
"price": 3,
"skuAttribute": "{\"就业薪资\":\"10K起\",\"学习费用\":\"2万\"}",
"spuId": "1318596430293704706",
"status": 1,
"updateTime": 1603184062000
},
{
"attrMap": {
"学习费用": "2万",
"就业薪资": "10K起"
},
"brandId": 11,
"brandName": "华为",
"categoryId": 11159,
"categoryName": "软件研发",
"createTime": 1603183717000,
"id": "1318594982227025922",
"image": "....",
"images": "....",
"name": "<span style=\"color:red\">华为</span>Mate40 Pro 32G",
"num": 122,
"price": 3,
"skuAttribute": "{\"就业薪资\":\"10K起\",\"学习费用\":\"2万\"}",
"spuId": "1318594982147334146",
"status": 1,
"updateTime": 1603183717000
}
],
"totalElements": 3
},
"message": "操作成功"
}