100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 尚硅谷微服务分布式电商项目《谷粒商城》-商品搜索

尚硅谷微服务分布式电商项目《谷粒商城》-商品搜索

时间:2021-10-07 19:11:58

相关推荐

尚硅谷微服务分布式电商项目《谷粒商城》-商品搜索

关注公众号:java星星 获取全套课件资料

1. 导入商品数据

1.1. 搭建搜索工程

pom.xml内容如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.atguigu</groupId><artifactId>gmall</artifactId><version>0.0.1-SNAPSHOT</version></parent><groupId>com.atguigu</groupId><artifactId>gmall-search</artifactId><version>0.0.1-SNAPSHOT</version><name>gmall-search</name><description>谷粒商城搜索系统</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>com.atguigu</groupId><artifactId>gmall-common</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>6.8.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

bootstrap.yml:

spring:application:name: search-servicecloud:nacos:config:server-addr: 127.0.0.1:8848

application.yml:

server:port: 18086spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848sentinel:transport:dashboard: localhost:8080port: 8719zipkin:base-url: http://localhost:9411discovery-client-enabled: falsesender:type: websleuth:sampler:probability: 1elasticsearch:rest:uris: http://172.16.116.100:9200feign:sentinel:enabled: truelogging:level:com.atguigu.gmall: debug

GmallSearchApplication.java引导类:

@SpringBootApplication@EnableDiscoveryClient@EnableFeignClientspublic class GmallSearchApplication {public static void main(String[] args) {SpringApplication.run(GmallSearchApplication.class, args);}}

网关路由:

- id: search-routeuri: lb://search-servicepredicates:- Host=

1.2. 构建es数据模型

接下来,我们需要商品数据导入索引库,便于用户搜索。

那么问题来了,我们有SPU和SKU,到底如何保存到索引库?

先看京东搜索,输入“小米”搜索:

可以看到每条记录就是一个sku,再来看看每条记录需要哪些字段:

直观能看到:sku的默认图片、sku的价格、标题、skuId

排序及筛选字段:综合、新品、销量、价格、库存(是否有货)等

聚合字段:品牌、分类、搜索规格参数(多个)

最终可以构建一个Goods对象:(注意属性名需要提前和前端约定好,不能随便改)

@Document(indexName = "goods", type = "info", shards = 3, replicas = 2)@Datapublic class Goods {@Idprivate Long skuId;@Field(type = FieldType.Keyword, index = false)private String pic;@Field(type = FieldType.Text, analyzer = "ik_max_word")private String title;@Field(type = FieldType.Double)private Double price;@Field(type = FieldType.Long)private Long sales; // 销量@Field(type = FieldType.Boolean)private Boolean store; // 是否有货@Field(type = FieldType.Date)private Date createTime; // 新品@Field(type = FieldType.Long)private Long brandId;@Field(type = FieldType.Keyword)private String brandName;@Field(type = FieldType.Long)private Long categoryId;@Field(type = FieldType.Keyword)private String categoryName;@Field(type = FieldType.Nested)private List<SearchAttrValue> attrs;}

搜索规格参数:SearchAttrValue

@Datapublic class SearchAttrValue {@Field(type = FieldType.Long)private Long attrId;@Field(type = FieldType.Keyword)private String attrName;@Field(type = FieldType.Keyword)private String attrValue;}

“type”: “nested”

嵌套结构,防止数据扁平化问题。

参照官方文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/nested-objects.html

1.3. 批量导入

1.3.1. 数据接口

索引库中的数据来自于数据库,我们不能直接去查询商品的数据库,因为真实开发中,每个微服务都是相互独立的,包括数据库也是一样。所以我们只能调用商品微服务提供的接口服务。

先思考我们需要的数据:

分页查询已上架的SPU信息根据SpuId查询对应的SKU信息(接口已写好)根据分类id查询商品分类(逆向工程已自动生成)根据品牌id查询品牌(逆向工程已自动生成)根据skuid查询库存(gmall-wms中接口已写好)根据spuId查询检索规格参数及值根据skuId查询检索规格参数及值

大部分接口之前已经编写完成,接下来开发接口:

1.3.1.1. 分页查询已上架SPU

在gmall-pms的SpuController中添加方法:

@PostMapping("page")public ResponseVo<List<SpuEntity>> querySpusByPage(@RequestBody PageParamVo pageParamVo){PageResultVo page = spuService.queryPage(pageParamVo);List<SpuEntity> list = (List<SpuEntity>)page.getList();return ResponseVo.ok(list);}

1.3.1.2. 根据spuId查询检索属性及值

在gmall-pms的SpuAttrValueController中添加方法:

@ApiOperation("根据spuId查询检索属性及值")@GetMapping("spu/{spuId}")public ResponseVo<List<SpuAttrValueEntity>> querySearchAttrValueBySpuId(@PathVariable("spuId")Long spuId){List<SpuAttrValueEntity> attrValueEntities = spuAttrValueService.querySearchAttrValueBySpuId(spuId);return ResponseVo.ok(attrValueEntities);}

在SpuAttrValueService中添加接口方法:

List<SpuAttrValueEntity> querySearchAttrValueBySpuId(Long spuId);

在实现类SpuAttrValueServiceImpl中添加实现方法:

@Autowiredprivate SpuAttrValueMapper spuAttrValueMapper;@Overridepublic List<SpuAttrValueEntity> querySearchAttrValueBySpuId(Long spuId) {return this.spuAttrValueMapper.querySearchAttrValueBySpuId(spuId);}

在SpuAttrValueMapper接口中添加接口方法:

@Mapperpublic interface SpuAttrValueMapper extends BaseMapper<SpuAttrValueEntity> {List<SpuAttrValueEntity> querySearchAttrValueBySpuId(Long spuId);}

在SpuAttrValueMapper对应的映射文件中添加配置:

<select id="querySearchAttrValueBySpuId" resultType="SpuAttrValueEntity">select a.id,a.attr_id,a.attr_name,a.attr_value,a.spu_idfrom pms_spu_attr_value a INNER JOIN pms_attr b on a.attr_id=b.idwhere a.spu_id=#{spuId} and b.search_type=1</select>

1.3.1.3. 根据skuId查询检索属性及值

在gmall-pms的SkuAttrValueController中添加方法:

@ApiOperation("根据spuId查询检索属性及值")@GetMapping("sku/{skuId}")public ResponseVo<List<SkuAttrValueEntity>> querySearchAttrValueBySkuId(@PathVariable("skuId")Long skuId){List<SkuAttrValueEntity> attrValueEntities = skuAttrValueService.querySearchAttrValueBySkuId(skuId);return ResponseVo.ok(attrValueEntities);}

在SkuAttrValueService中添加接口方法:

List<SkuAttrValueEntity> querySearchAttrValueBySkuId(Long skuId);

在实现类SkuAttrValueServiceImpl中添加实现方法:

@Autowiredprivate SkuAttrValueMapper skuAttrValueMapper;@Overridepublic List<SkuAttrValueEntity> querySearchAttrValueBySkuId(Long skuId) {return this.skuAttrValueMapper.querySearchAttrValueBySkuId(skuId);}

在SkuAttrValueMapper接口中添加接口方法:

@Mapperpublic interface SkuAttrValueMapper extends BaseMapper<SkuAttrValueEntity> {List<SkuAttrValueEntity> querySearchAttrValueBySkuId(Long skuId);}

在SpuAttrValueMapper对应的映射文件中添加配置:

<select id="querySearchAttrValueBySkuId" resultType="SkuAttrValueEntity">select a.id,a.attr_id,a.attr_name,a.attr_value,a.sku_idfrom pms_sku_attr_value a INNER JOIN pms_attr b on a.attr_id=b.idwhere a.sku_id=#{skuId} and b.search_type=1</select>

1.3.2. 编写接口工程

参考gmall-sms-interface,创建gmall-pms-interface及gmall-wms-interface

把gmall-wms和gmall-pms这两个工程中对应的entity都copy过来,方便通用。原先工程对应的entity就不用了,例如gmall-pms:

这时,gmall-pms的pom.xml中需要引入gmall-pms-interface的依赖。gmall-wms也是相同的处理方式。

GmallPmsApi:

public interface GmallPmsApi {@PostMapping("pms/spu/page")public ResponseVo<List<SpuEntity>> querySpusByPage(@RequestBody PageParamVo pageParamVo);@GetMapping("pms/sku/spu/{spuId}")public ResponseVo<List<SkuEntity>> querySkusBySpuId(@PathVariable("spuId")Long spuId);@GetMapping("pms/category/{id}")public ResponseVo<CategoryEntity> queryCategoryById(@PathVariable("id") Long id);@GetMapping("pms/brand/{id}")public ResponseVo<BrandEntity> queryBrandById(@PathVariable("id") Long id);@GetMapping("pms/spuattrvalue/spu/{spuId}")public ResponseVo<List<SpuAttrValueEntity>> querySearchAttrValueBySpuId(@PathVariable("spuId")Long spuId);@GetMapping("pms/skuattrvalue/sku/{skuId}")public ResponseVo<List<SkuAttrValueEntity>> querySearchAttrValueBySkuId(@PathVariable("skuId")Long skuId);}

GmallWmsApi:

public interface GmallWmsApi {@GetMapping("wms/waresku/sku/{skuId}")public ResponseVo<List<WareSkuEntity>> queryWareSkuBySkuId(@PathVariable("skuId")Long skuId);}

1.3.3. 使用feign调用远程接口

在gmall-search中引入依赖:

<dependency><groupId>com.atguigu</groupId><artifactId>gmall-pms-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>com.atguigu</groupId><artifactId>gmall-wms-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency>

在gmall-search中添加feign接口:

GmallPmsClient:

@FeignClient("pms-service")public interface GmallPmsClient extends GmallPmsApi {}

GmallWmsClient:

@FeignClient("wms-service")public interface GmallWmsClient extends GmallWmsApi {}

1.3.4. 导入数据

编写GoodsRepository:

内容如下:

public interface GoodsRepository extends ElasticsearchRepository<Goods, Long> {}

由于数据导入只需导入一次,这里就写一个测试用例。后续索引库和数据库的数据同步,通过程序自身来维护。

在测试用例中导入数据:

@Testvoid importData(){// 创建索引及映射this.restTemplate.createIndex(Goods.class);this.restTemplate.putMapping(Goods.class);Integer pageNum = 1;Integer pageSize = 100;do {// 分页查询spuPageParamVo pageParamVo = new PageParamVo();pageParamVo.setPageNum(pageNum);pageParamVo.setPageSize(pageSize);ResponseVo<List<SpuEntity>> spuResp = this.pmsClient.querySpusByPage(pageParamVo);List<SpuEntity> spus = spuResp.getData();// 遍历spu,查询skuspus.forEach(spuEntity -> {ResponseVo<List<SkuEntity>> skuResp = this.pmsClient.querySkusBySpuId(spuEntity.getId());List<SkuEntity> skuEntities = skuResp.getData();if (!CollectionUtils.isEmpty(skuEntities)){// 把sku转化成goods对象List<Goods> goodsList = skuEntities.stream().map(skuEntity -> {Goods goods = new Goods();// 查询spu搜索属性及值ResponseVo<List<SpuAttrValueEntity>> attrValueResp = this.pmsClient.querySearchAttrValueBySpuId(spuEntity.getId());List<SpuAttrValueEntity> attrValueEntities = attrValueResp.getData();List<SearchAttrValue> searchAttrValues = new ArrayList<>();if (!CollectionUtils.isEmpty(attrValueEntities)) {searchAttrValues = attrValueEntities.stream().map(spuAttrValueEntity -> {SearchAttrValue searchAttrValue = new SearchAttrValue();searchAttrValue.setAttrId(spuAttrValueEntity.getAttrId());searchAttrValue.setAttrName(spuAttrValueEntity.getAttrName());searchAttrValue.setAttrValue(spuAttrValueEntity.getAttrValue());return searchAttrValue;}).collect(Collectors.toList());}// 查询sku搜索属性及值ResponseVo<List<SkuAttrValueEntity>> skuAttrValueResp = this.pmsClient.querySearchAttrValueBySkuId(spuEntity.getId());List<SkuAttrValueEntity> skuAttrValueEntities = skuAttrValueResp.getData();List<SearchAttrValue> searchSkuAttrValues = new ArrayList<>();if (!CollectionUtils.isEmpty(skuAttrValueEntities)) {searchSkuAttrValues = skuAttrValueEntities.stream().map(skuAttrValueEntity -> {SearchAttrValue searchAttrValue = new SearchAttrValue();searchAttrValue.setAttrId(skuAttrValueEntity.getAttrId());searchAttrValue.setAttrName(skuAttrValueEntity.getAttrName());searchAttrValue.setAttrValue(skuAttrValueEntity.getAttrValue());return searchAttrValue;}).collect(Collectors.toList());}searchAttrValues.addAll(searchSkuAttrValues);goods.setAttrs(searchAttrValues);// 查询品牌ResponseVo<BrandEntity> brandEntityResp = this.pmsClient.queryBrandById(skuEntity.getBrandId());BrandEntity brandEntity = brandEntityResp.getData();if (brandEntity != null){goods.setBrandId(skuEntity.getBrandId());goods.setBrandName(brandEntity.getName());}// 查询分类ResponseVo<CategoryEntity> categoryEntityResp = this.pmsClient.queryCategoryById(skuEntity.getCatagoryId());CategoryEntity categoryEntity = categoryEntityResp.getData();if (categoryEntity != null) {goods.setCategoryId(skuEntity.getCatagoryId());goods.setCategoryName(categoryEntity.getName());}goods.setCreateTime(spuEntity.getCreateTime());goods.setPic(skuEntity.getDefaultImage());goods.setPrice(skuEntity.getPrice().doubleValue());goods.setSales(0l);goods.setSkuId(skuEntity.getId());// 查询库存信息ResponseVo<List<WareSkuEntity>> listResp = this.wmsClient.queryWareSkuBySkuId(skuEntity.getId());List<WareSkuEntity> wareSkuEntities = listResp.getData();if (!CollectionUtils.isEmpty(wareSkuEntities)) {boolean flag = wareSkuEntities.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() > 0);goods.setStore(flag);}goods.setTitle(skuEntity.getTitle());return goods;}).collect(Collectors.toList());this.goodsRepository.saveAll(goodsList);}});// 导入索引库pageSize = spus.size();pageNum++;} while (pageSize == 100);}

2. 基本检索

前端根据用户输入的查询、过滤、排序、分页条件等,后台生成DSL,查询出最终结果响应给前端,渲染页面展示给用户。

2.1. 编写完整的DSL

GET /goods/_search{"query": {"bool": {"must": [{"match": {"title": {"query": "手机","operator": "and"}}}],"filter": [{"nested": {"path": "attrs","query": {"bool": {"must": [{"term": {"attrs.attrId": {"value": "9"}}},{"terms": {"attrs.attrValue": ["5","6","7"]}}]}}}},{"nested": {"path": "attrs","query": {"bool": {"must": [{"term": {"attrs.attrId": {"value": "4"}}},{"terms": {"attrs.attrValue": ["8G", "12G"]}}]}}}},{"terms": {"brandId": [1,2,3]}},{"terms": {"categoryId": [225]}},{"range": {"price": {"gte": 0,"lte": 10000}}}]}},"from": 0,"size": 10,"highlight": {"fields": {"name": {}},"pre_tags": "<b style='color:red'>","post_tags": "</b>"},"sort": [{"price": {"order": "desc"}}],"aggs": {"attr_agg": {"nested": {"path": "attrs"},"aggs": {"attrIdAgg": {"terms": {"field": "attrs.attrId"},"aggs": {"attrNameAgg": {"terms": {"field": "attrs.attrName"}},"attrValueAgg": {"terms": {"field": "attrs.attrValue"}}}}}},"brandIdAgg": {"terms": {"field": "brandId"},"aggs": {"brandNameAgg": {"terms": {"field": "brandName"}}}},"categoryIdAgg": {"terms": {"field": "categoryId"},"aggs": {"categoryNameAgg": {"terms": {"field": "categoryName"}}}}}}

2.2. 封装前端检索条件

参照京东:

参照京东,设计模型如下:

内容:

/*** 接受页面传递过来的检索参数* search?keyword=小米&brandId=1,3&cid=225&props=5:高通-麒麟&props=6:骁龙865-硅谷1000&sort=1&priceFrom=1000&priceTo=6000&pageNum=1&store=true**/@Datapublic class SearchParamVo {private String keyword; // 检索条件private List<Long> brandId; // 品牌过滤private Long cid; // 分类过滤// props=5:高通-麒麟,6:骁龙865-硅谷1000private List<String> props; // 过滤的检索参数private Integer sort = 0;// 排序字段:0-默认,得分降序;1-按价格升序;2-按价格降序;3-按创建时间降序;4-按销量降序// 价格区间private Double priceFrom;private Double priceTo;private Integer pageNum = 1; // 页码private final Integer pageSize = 20; // 每页记录数private Boolean store; // 是否有货}

2.3. 搜索的业务逻辑

ElasticSearchConfig:

@Configurationpublic class ElasticsearchConfig {@Beanpublic RestHighLevelClient restHighLevelClient(){return new RestHighLevelClient(RestClient.builder(HttpHost.create("172.16.116.100:9200")));}}

SearchController:

@RestController@RequestMapping("search")public class SearchController {@Autowiredprivate SearchService searchService;@GetMappingpublic Resp<Object> search(SearchParamVo searchParam) throws IOException {this.searchService.search(searchParam);return Resp.ok(null);}}

SearchService:

@Servicepublic class SearchService {@Autowiredprivate RestHighLevelClient restHighLevelClient;public void search(SearchParamVo paramVo) {try {SearchRequest searchRequest = new SearchRequest(new String[]{"goods"}, buildDsl(paramVo));SearchResponse response = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}}/*** 构建查询DSL语句* @return*/private SearchSourceBuilder buildDsl(SearchParamVo paramVo) {SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();String keyword = paramVo.getKeyword();if (StringUtils.isEmpty(keyword)){// 打广告,TODOreturn null;}// 1. 构建查询条件(bool查询)BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 1.1. 匹配查询boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword).operator(Operator.AND));// 1.2. 过滤// 1.2.1. 品牌过滤List<Long> brandId = paramVo.getBrandId();if (!CollectionUtils.isEmpty(brandId)){boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brandId));}// 1.2.2. 分类过滤Long cid = paramVo.getCid();if (cid != null) {boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", cid));}// 1.2.3. 价格区间过滤Double priceFrom = paramVo.getPriceFrom();Double priceTo = paramVo.getPriceTo();if (priceFrom != null || priceTo != null){RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");if (priceFrom != null){rangeQuery.gte(priceFrom);}if (priceTo != null) {rangeQuery.lte(priceTo);}boolQueryBuilder.filter(rangeQuery);}// 1.2.4. 是否有货Boolean store = paramVo.getStore();if (store != null) {boolQueryBuilder.filter(QueryBuilders.termQuery("store", store));}// 1.2.5. 规格参数的过滤 props=5:高通-麒麟,6:骁龙865-硅谷1000List<String> props = paramVo.getProps();if (!CollectionUtils.isEmpty(props)){props.forEach(prop -> {String[] attrs = StringUtils.split(prop, ":");if (attrs != null && attrs.length == 2) {String attrId = attrs[0];String attrValueString = attrs[1];String[] attrValues = StringUtils.split(attrValueString, "-");BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();boolQuery.must(QueryBuilders.termQuery("searchAttrs.attrId", attrId));boolQuery.must(QueryBuilders.termsQuery("searchAttrs.attrValue", attrValues));boolQueryBuilder.filter(QueryBuilders.nestedQuery("searchAttrs", boolQuery, ScoreMode.None));}});}sourceBuilder.query(boolQueryBuilder);// 2. 构建排序 0-默认,得分降序;1-按价格升序;2-按价格降序;3-按创建时间降序;4-按销量降序Integer sort = paramVo.getSort();String field = "";SortOrder order = null;switch (sort){case 1: field = "price"; order = SortOrder.ASC; break;case 2: field = "price"; order = SortOrder.DESC; break;case 3: field = "createTime"; order = SortOrder.DESC; break;case 4: field = "sales"; order = SortOrder.DESC; break;default: field = "_score"; order = SortOrder.DESC; break;}sourceBuilder.sort(field, order);// 3. 构建分页Integer pageNum = paramVo.getPageNum();Integer pageSize = paramVo.getPageSize();sourceBuilder.from((pageNum - 1) * pageSize);sourceBuilder.size(pageSize);// 4. 构建高亮sourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<font style='color:red'>").postTags("</font>"));// 5. 构建聚合// 5.1. 构建品牌聚合sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId").subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")).subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));// 5.2. 构建分类聚合sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId").subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));// 5.3. 构建规格参数的嵌套聚合sourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "searchAttrs").subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId").subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName")).subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));// 6. 构建结果集过滤sourceBuilder.fetchSource(new String[]{"skuId", "title", "price", "defaultImage"}, null);System.out.println(sourceBuilder.toString());return sourceBuilder;}}

2.4. 响应的数据模型

虽然实现了搜索的业务过程,但是,还没有对搜索后的结果进行封装。首先响应数据的数据模型参考笔记目录下的

copy到gmall-search工程的vo包下:

2.5. 完成搜索功能

封装之前,先启动搜索微服务,并在RestClient工具(例如:Postman)中访问,看看控制台打印的搜索结果

在地址栏访问:输入一个有数据的关键字条件

打印出的结果:放到jsonviewer工具查看,发现数据结构跟kibana几乎一模一样。直接参考kibana即可完成对数据的封装。

最终完成代码,如下

SearchController:

@RestController@RequestMapping("search")public class SearchController {@Autowiredprivate SearchService searchService;@GetMappingpublic ResponseVo<SearchResponseVo> search(SearchParam searchParam) throws IOException {SearchResponseVo responseVO = this.searchService.search(searchParam);return ResponseVo.ok(responseVO);}}

SearchService:

@Servicepublic class SearchService {@Autowiredprivate RestHighLevelClient restHighLevelClient;public SearchResponseVo search(SearchParamVo paramVo) {try {// 构建查询条件SearchRequest searchRequest = new SearchRequest(new String[]{"goods"}, this.buildDsl(paramVo));// 执行查询SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);// 解析结果集SearchResponseVo responseVo = this.parseResult(searchResponse);responseVo.setPageNum(paramVo.getPageNum());responseVo.setPageSize(paramVo.getPageSize());return responseVo;} catch (IOException e) {e.printStackTrace();}return null;}/*** 解析搜索结果集* @param response* @return*/private SearchResponseVo parseResult(SearchResponse response) {SearchResponseVo responseVo = new SearchResponseVo();SearchHits hits = response.getHits();// 总命中的记录数responseVo.setTotal(hits.getTotalHits());SearchHit[] hitsHits = hits.getHits();List<Goods> goodsList = Stream.of(hitsHits).map(hitsHit -> {// 获取内层hits的_source 数据String goodsJson = hitsHit.getSourceAsString();// 反序列化为goods对象Goods goods = JSON.parseObject(goodsJson, Goods.class);// 获取高亮的title覆盖掉普通titleMap<String, HighlightField> highlightFields = hitsHit.getHighlightFields();HighlightField highlightField = highlightFields.get("title");String highlightTitle = highlightField.getFragments()[0].toString();goods.setTitle(highlightTitle);return goods;}).collect(Collectors.toList());responseVo.setGoodsList(goodsList);// 聚合结果集的解析Map<String, Aggregation> aggregationMap = response.getAggregations().asMap();// 1. 解析聚合结果集,获取品牌》// {attrId: null, attrName: "品牌", attrValues: [{id: 1, name: 尚硅谷, logo: /logo.gif}, {}]}ParsedLongTerms brandIdAgg = (ParsedLongTerms)aggregationMap.get("brandIdAgg");List<? extends Terms.Bucket> buckets = brandIdAgg.getBuckets();if (!CollectionUtils.isEmpty(buckets)){List<BrandEntity> brands = buckets.stream().map(bucket -> {// {id: 1, name: 尚硅谷, logo: /logo.gif}// 为了得到指定格式的json字符串,创建了一个mapBrandEntity brandEntity = new BrandEntity();// 获取brandIdAgg中的key,这个key就是品牌的idLong brandId = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();brandEntity.setId(brandId);// 解析品牌名称的子聚合,获取品牌名称Map<String, Aggregation> brandAggregationMap = ((Terms.Bucket) bucket).getAggregations().asMap();ParsedStringTerms brandNameAgg = (ParsedStringTerms)brandAggregationMap.get("brandNameAgg");brandEntity.setName(brandNameAgg.getBuckets().get(0).getKeyAsString());// 解析品牌logo的子聚合,获取品牌 的logoParsedStringTerms logoAgg = (ParsedStringTerms)brandAggregationMap.get("logoAgg");List<? extends Terms.Bucket> logoAggBuckets = logoAgg.getBuckets();if (!CollectionUtils.isEmpty(logoAggBuckets)){brandEntity.setLogo(logoAggBuckets.get(0).getKeyAsString());}// 把map反序列化为json字符串return brandEntity;}).collect(Collectors.toList());responseVo.setBrands(brands);}// 2. 解析聚合结果集,获取分类ParsedLongTerms categoryIdAgg = (ParsedLongTerms)aggregationMap.get("categoryIdAgg");List<? extends Terms.Bucket> categoryIdAggBuckets = categoryIdAgg.getBuckets();if (!CollectionUtils.isEmpty(categoryIdAggBuckets)){List<CategoryEntity> categories = categoryIdAggBuckets.stream().map(bucket -> {// {id: 225, name: 手机}CategoryEntity categoryEntity = new CategoryEntity();// 获取bucket的key,key就是分类的idLong categoryId = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();categoryEntity.setId(categoryId);// 解析分类名称的子聚合,获取分类名称ParsedStringTerms categoryNameAgg = (ParsedStringTerms)((Terms.Bucket) bucket).getAggregations().get("categoryNameAgg");categoryEntity.setName(categoryNameAgg.getBuckets().get(0).getKeyAsString());return categoryEntity;}).collect(Collectors.toList());responseVo.setCategories(categories);}// 3. 解析聚合结果集,获取规格参数ParsedNested attrAgg = (ParsedNested)aggregationMap.get("attrAgg");ParsedLongTerms attrIdAgg = (ParsedLongTerms)attrAgg.getAggregations().get("attrIdAgg");List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();if (!CollectionUtils.isEmpty(attrIdAggBuckets)) {List<SearchResponseAttrVo> filters = attrIdAggBuckets.stream().map(bucket -> {SearchResponseAttrVo responseAttrVo = new SearchResponseAttrVo();// 规格参数idresponseAttrVo.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());// 规格参数的名称ParsedStringTerms attrNameAgg = (ParsedStringTerms)((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");responseAttrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());// 规格参数值ParsedStringTerms attrValueAgg = (ParsedStringTerms)((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");List<? extends Terms.Bucket> attrValueAggBuckets = attrValueAgg.getBuckets();if (!CollectionUtils.isEmpty(attrValueAggBuckets)){List<String> attrValues = attrValueAggBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());responseAttrVo.setAttrValues(attrValues);}return responseAttrVo;}).collect(Collectors.toList());responseVo.setFilters(filters);}return responseVo;}/*** 构建查询DSL语句* @return*/private SearchSourceBuilder buildDsl(SearchParamVo paramVo) {SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();String keyword = paramVo.getKeyword();if (StringUtils.isEmpty(keyword)){// 打广告,TODOreturn null;}// 1. 构建查询条件(bool查询)BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 1.1. 匹配查询boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword).operator(Operator.AND));// 1.2. 过滤// 1.2.1. 品牌过滤List<Long> brandId = paramVo.getBrandId();if (!CollectionUtils.isEmpty(brandId)){boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brandId));}// 1.2.2. 分类过滤Long cid = paramVo.getCid();if (cid != null) {boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", cid));}// 1.2.3. 价格区间过滤Double priceFrom = paramVo.getPriceFrom();Double priceTo = paramVo.getPriceTo();if (priceFrom != null || priceTo != null){RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");if (priceFrom != null){rangeQuery.gte(priceFrom);}if (priceTo != null) {rangeQuery.lte(priceTo);}boolQueryBuilder.filter(rangeQuery);}// 1.2.4. 是否有货Boolean store = paramVo.getStore();if (store != null) {boolQueryBuilder.filter(QueryBuilders.termQuery("store", store));}// 1.2.5. 规格参数的过滤 props=5:高通-麒麟,6:骁龙865-硅谷1000List<String> props = paramVo.getProps();if (!CollectionUtils.isEmpty(props)){props.forEach(prop -> {String[] attrs = StringUtils.split(prop, ":");if (attrs != null && attrs.length == 2) {String attrId = attrs[0];String attrValueString = attrs[1];String[] attrValues = StringUtils.split(attrValueString, "-");BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();boolQuery.must(QueryBuilders.termQuery("searchAttrs.attrId", attrId));boolQuery.must(QueryBuilders.termsQuery("searchAttrs.attrValue", attrValues));boolQueryBuilder.filter(QueryBuilders.nestedQuery("searchAttrs", boolQuery, ScoreMode.None));}});}sourceBuilder.query(boolQueryBuilder);// 2. 构建排序 0-默认,得分降序;1-按价格升序;2-按价格降序;3-按创建时间降序;4-按销量降序Integer sort = paramVo.getSort();String field = "";SortOrder order = null;switch (sort){case 1: field = "price"; order = SortOrder.ASC; break;case 2: field = "price"; order = SortOrder.DESC; break;case 3: field = "createTime"; order = SortOrder.DESC; break;case 4: field = "sales"; order = SortOrder.DESC; break;default: field = "_score"; order = SortOrder.DESC; break;}sourceBuilder.sort(field, order);// 3. 构建分页Integer pageNum = paramVo.getPageNum();Integer pageSize = paramVo.getPageSize();sourceBuilder.from((pageNum - 1) * pageSize);sourceBuilder.size(pageSize);// 4. 构建高亮sourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<font style='color:red'>").postTags("</font>"));// 5. 构建聚合// 5.1. 构建品牌聚合sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId").subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")).subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));// 5.2. 构建分类聚合sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId").subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));// 5.3. 构建规格参数的嵌套聚合sourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "searchAttrs").subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId").subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName")).subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));// 6. 构建结果集过滤sourceBuilder.fetchSource(new String[]{"skuId", "title", "price", "defaultImage"}, null);System.out.println(sourceBuilder.toString());return sourceBuilder;}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。