尚品汇-商品详情检索功能实现

一、controller层

@RestController
@RequestMapping("/api/inner/rpc/search")
public class SearchRpcController {

    @Resource
    SearchService searchService;

    /**
     * 商品检索
     * @param searchParamVo
     * @return
     */
    @PostMapping("/searchgoods")
    public Result<SearchRespVo> search(@RequestBody SearchParamVo searchParamVo){
        SearchRespVo searchRespVo=searchService.search(searchParamVo);
        return Result.ok(searchRespVo);
    }
    /**
     * 下架
     * @param skuId
     * @return
     */
    @GetMapping("/down/goods/{skuId}")
    public Result down(@PathVariable Long skuId){
        searchService.down(skuId);
        return Result.ok();
    }

    /**
     * 上架
     * @param goods
     * @return
     */
    @PostMapping("up/goods")
    public Result up(@RequestBody Goods goods){
        searchService.up(goods);
        return Result.ok();
    }

    /**
     * 增加热度分
     */
    @GetMapping("/hotscore/{skuId}/{score}")
    public Result updateHotScore(@PathVariable Long skuId,@PathVariable Long score){
        searchService.updateHotScore(skuId,score);
        return Result.ok();
    }

二、service层

public interface SearchService {
    /**
     * 商品下架
     * @param skuId
     */
    void down(Long skuId);

    /**
     * 商品上架
     * @param goods
     */
    void up(Goods goods);

    /**
     * 检索商品
     * @param searchParamVo
     * @return
     */
    SearchRespVo search(SearchParamVo searchParamVo);

    /**
     * 热度分
     * @param skuId
     * @param score
     */
    void updateHotScore(Long skuId, Long score);
}

三、service的实现层

@Service
public class SearchServiceImpl implements SearchService {

    @Resource
    GoodsRepositories goodsRepositories;

    @Resource
    ElasticsearchRestTemplate restTemplate;

    int pageSize=10;

    @Override
    public void down(Long skuId) {
        goodsRepositories.deleteById(skuId);
    }

    @Override
    public void up(Goods goods) {
        goodsRepositories.save(goods);
    }

    @Override
    public SearchRespVo search(SearchParamVo searchParamVo) {

        //根据前端请求参数,构建查询条件
        Query query = getQuery(searchParamVo);
        //检索结果
        SearchHits<Goods> goods = restTemplate.search(query,
                Goods.class,
                IndexCoordinates.of("goods"));
        //解析得到的数据并封装返回前端
        SearchRespVo respVo=builderSearchResp(goods,searchParamVo);
        return respVo;
    }

    @Override
    public void updateHotScore(Long skuId, Long score) {
        Document document = Document.create();
        document.put("hotScore",score);
        UpdateQuery query = UpdateQuery.builder(skuId + "")
                .withDocAsUpsert(true)
                .withDocument(document).build();
        restTemplate.update(query,IndexCoordinates.of("goods"));
    }

    private SearchRespVo builderSearchResp(SearchHits<Goods> goods,SearchParamVo searchParamVo) {

        SearchRespVo respVo = new SearchRespVo();

        //检索参数
        respVo.setSearchParamVo(searchParamVo);

        //品牌面包屑
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
            respVo.setTrademarkParam("品牌: "+searchParamVo.getTrademark().split(":")[1]);
        }

        //属性面包屑
        if (searchParamVo.getProps()!=null && searchParamVo.getProps().length>0) {
            List<SearchRespVo.Props> propsList = Arrays.stream(searchParamVo.getProps())
                    .map(item -> {
                        String[] split = item.split(":");
                        SearchRespVo.Props props = new SearchRespVo.Props();
                        props.setAttrName(split[2]);
                        props.setAttrValue(split[1]);
                        props.setAttrId(Long.parseLong(split[0]));
                        return props;
                    }).collect(Collectors.toList());
            respVo.setPropsParamList(propsList);
        }

        //品牌列表 聚合 aggregations
        ParsedLongTerms tmIdAgg = goods.getAggregations().get("tmIdAgg");
        List<SearchRespVo.Trademark> trademarks = tmIdAgg.getBuckets().stream()
                .map(bucket -> {
                    SearchRespVo.Trademark trademark = new SearchRespVo.Trademark();
                    //tmid
                    trademark.setTmId(bucket.getKeyAsNumber().longValue());
                    ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
                    String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
                    //tmName
                    trademark.setTmName(tmName);
                    ParsedStringTerms tmLogoAgg = bucket.getAggregations().get("tmLogoAgg");
                    String tmLogoUrl = tmLogoAgg.getBuckets().get(0).getKeyAsString();
                    //tmLogoUrl
                    trademark.setTmLogoUrl(tmLogoUrl);
                    return trademark;
                }).collect(Collectors.toList());
        respVo.setTrademarkList(trademarks);
        //属性列表 聚合 aggregations

        ParsedNested attrsAgg = goods.getAggregations().get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");

        List<SearchRespVo.Arrts> arrtsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchRespVo.Arrts arrts = new SearchRespVo.Arrts();
            //attrId
            long attrId = bucket.getKeyAsNumber().longValue();
            arrts.setAttrId(attrId);
            //attrName
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            arrts.setAttrName(attrName);
            //AttrValueList
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> stringList = attrValueAgg.getBuckets().stream()
                    .map(item -> item.getKeyAsString()).collect(Collectors.toList());
            arrts.setAttrValueList(stringList);
            return arrts;
        }).collect(Collectors.toList());
        respVo.setAttrsList(arrtsList);

        //url参数
        String urlParam = builderUrlParam(searchParamVo);
        respVo.setUrlParam(urlParam);
        //排序信息
        if (!StringUtils.isEmpty(searchParamVo.getOrder())){
            OrderMap orderMap = new OrderMap();
            String[] split = searchParamVo.getOrder().split(":");
            orderMap.setSort(split[1]);
            orderMap.setType(split[0]);
            respVo.setOrderMap(orderMap);
        }

        //商品列表
        List<Goods> goodsList = goods.getSearchHits().stream()
                                .map(good -> {
                                    Goods content = good.getContent();
                                    if(!StringUtils.isEmpty(searchParamVo.getKeyword())){
                                        String newTitle = good.getHighlightField("title").get(0);
                                        content.setTitle(newTitle);
                                    }
                                   return content;
                                })
                                .collect(Collectors.toList());
        respVo.setGoodsList(goodsList);
        //页码
        respVo.setPageNo(searchParamVo.getPageNo());

        //总记录数
        //总页码
        long totalHits = goods.getTotalHits();
        respVo.setTotalPages(totalHits % pageSize == 0 ? totalHits / pageSize : totalHits / pageSize + 1);
        return respVo;
    }

    private String builderUrlParam(SearchParamVo searchParamVo) {
        StringBuilder builder=new StringBuilder("list.html?");
        if(searchParamVo.getCategory1Id()!=null){
            builder.append("&category1Id="+searchParamVo.getCategory1Id());
        }
        if(searchParamVo.getCategory2Id()!=null){
            builder.append("&category2Id="+searchParamVo.getCategory2Id());
        }
        if(searchParamVo.getCategory3Id()!=null){
            builder.append("&category3Id="+searchParamVo.getCategory3Id());
        }

        if (!StringUtils.isEmpty(searchParamVo.getKeyword())){
            builder.append("&keyword="+searchParamVo.getKeyword());
        }
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
            builder.append("&trademark="+searchParamVo.getTrademark());
        }
        //属性
        if(searchParamVo.getProps()!=null && searchParamVo.getProps().length>0){
            Arrays.stream(searchParamVo.getProps()).forEach(item->{
                builder.append("&props="+item);
            });
        }
        return builder.toString();
    }

    private Query getQuery(SearchParamVo searchParamVo) {
        //查询条件
        BoolQueryBuilder bool = QueryBuilders.boolQuery();
        //1.1、一级分类
        if (searchParamVo.getCategory1Id() != null) {
            bool.must(QueryBuilders.termQuery("category1Id", searchParamVo.getCategory1Id()));
        }
        //1.2、二级分类
        if (searchParamVo.getCategory2Id() != null) {
            bool.must(QueryBuilders.termQuery("category2Id", searchParamVo.getCategory2Id()));
        }
        //1.3、三级分类
        if (searchParamVo.getCategory3Id() != null) {
            bool.must(QueryBuilders.termQuery("category3Id", searchParamVo.getCategory3Id()));
        }
        //1.4、关键字
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            bool.must(QueryBuilders.matchQuery("title", searchParamVo.getKeyword()));
        }

        //1.5、品牌查询
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())) {
            String[] split = searchParamVo.getTrademark().split(":");
            bool.must(QueryBuilders.termQuery("tmId", split[0]));
        }

        //1.6、属性检索
        if (searchParamVo.getProps() != null && searchParamVo.getProps().length > 0) {
            Arrays.stream(searchParamVo.getProps())
                    .forEach(item->{
                        String[] split = item.split(":");
                        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

                        //属性id
                        boolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                        //属性值条件
                        boolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                        NestedQueryBuilder nested = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
                        bool.must(nested);
                    });
        }
        NativeSearchQuery query = new NativeSearchQuery(bool);
        //排序方式
        if (!StringUtils.isEmpty(searchParamVo.getOrder())) {
            Sort sort = null;

            String[] split = searchParamVo.getOrder().split(":");
            Sort.Direction direction= "asc".equalsIgnoreCase(split[1])?Sort.Direction.ASC:Sort.Direction.DESC;
            switch (split[0]){
                case "1":
                    sort=Sort.by(direction, "hotScore");
                    break;
                case "2":
                    sort=Sort.by(direction, "price");
                    break;
                default:
                    sort=Sort.by(Sort.Direction.DESC, "hotScore");
            }
            query.addSort(sort);
        }

        //分页
        Pageable pageable=PageRequest.of(searchParamVo.getPageNo()-1,pageSize);
        query.setPageable(pageable);

        //品牌id聚合
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg")
                .field("tmId")
                .size(200);
        //name子聚合
        tmIdAgg.subAggregation(AggregationBuilders.terms("tmNameAgg")
                .field("tmName")
                .size(1));
        //value自聚合
        tmIdAgg.subAggregation(AggregationBuilders.terms("tmLogoAgg")
                .field("tmLogoUrl")
                .size(1));
        query.addAggregation(tmIdAgg);

        //品牌属性值聚合
        NestedAggregationBuilder attrsAgg = AggregationBuilders.nested("attrsAgg", "attrs");
        //attrId聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg")
                .field("attrs.attrId")
                .size(200);
        //name聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg")
                .field("attrs.attrName")
                .size(1));
        //value聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg")
                .field("attrs.attrValue")
                .size(200));
        attrsAgg.subAggregation(attrIdAgg);
        query.addAggregation(attrsAgg);

        //高亮
        if(!StringUtils.isEmpty(searchParamVo.getKeyword())){
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("title")
                    .preTags("")
                    .postTags("");
            HighlightQuery highlightQuery = new HighlightQuery(builder);
            query.setHighlightQuery(highlightQuery);
        }
        return query;
    }
}

四、对应的es的DLS语句

# 注dls语句只有部分语句,具体请参考service的实现代码
GET goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "category3Id": {
              "value": "61"
            }
          }
        },
        {
          "match": {
            "title": "小米"
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 10,
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ],
  "aggs": {
    "tmIdAgg": {
      "terms": {
        "field": "tmId",
        "size": 200
      },
      "aggs": {
        "tmNameAgg": {
          "terms": {
            "field": "tmName",
            "size": 1
          }
        },
        "tmValueAgg":{
          "terms": {
            "field": "tmLogoUrl",
            "size": 1
          }
        }
      }
    },
    "attrsAgg":{
      "nested": {
        "path": "attrs"
      },
      "aggs": {
        "attrIdAgg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 200
          },
          "aggs": {
            "attrNameAgg": {
              "terms": {
                "field": "attrs.attrName",
                "size": 1
              }
            },
            "attrValueAgg":{
              "terms": {
                "field": "attrs.attrValue",
                "size": 200
              }
            }
          }
        }
      }
    }
  },
  "highlight": {
   "fields": {
     "title": {
       "pre_tags": "",
       "post_tags": ""
     }
   }
 }
}

五、GoodsRepositories类

@Repository
public interface GoodsRepositories extends PagingAndSortingRepository<Goods,Long> {
}

六 ,对应的实体类

Goods实体类

@Data
@Document(indexName = "goods" , shards = 3,replicas = 2)
public class Goods {
    // 商品Id skuId
    @Id
    private Long id;

    @Field(type = FieldType.Keyword, index = false)
    private String defaultImg;

    //  es 中能分词的字段,这个字段数据类型必须是 text!keyword 不分词!
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;

    @Field(type = FieldType.Double)
    private Double price;

    //  @Field(type = FieldType.Date)   6.8.1
    @Field(type = FieldType.Date,format = DateFormat.custom,pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime; // 新品

    @Field(type = FieldType.Long)
    private Long tmId;

    @Field(type = FieldType.Keyword)
    private String tmName;

    @Field(type = FieldType.Keyword)
    private String tmLogoUrl;

    @Field(type = FieldType.Long)
    private Long category1Id;

    @Field(type = FieldType.Keyword)
    private String category1Name;

    @Field(type = FieldType.Long)
    private Long category2Id;

    @Field(type = FieldType.Keyword)
    private String category2Name;

    @Field(type = FieldType.Long)
    private Long category3Id;

    @Field(type = FieldType.Keyword)
    private String category3Name;

    //  商品的热度! 我们将商品被用户点查看的次数越多,则说明热度就越高!
    @Field(type = FieldType.Long)
    private Long hotScore = 0L;

    // 平台属性集合对象
    // Nested 支持嵌套查询
    @Field(type = FieldType.Nested)
    private List<SearchAttr> attrs;

}

SearchAttr实体类

@Data
public class SearchAttr {
    // 平台属性Id
    @Field(type = FieldType.Long)
    private Long attrId;
    // 平台属性值名称
    @Field(type = FieldType.Keyword)
    private String attrValue;
    // 平台属性名
    @Field(type = FieldType.Keyword)
    private String attrName;

}

SearchParamVo

@Data
public class SearchParamVo {
    Long category1Id;
    Long category2Id;
    Long category3Id;
    //以上是分类相关参数
    /**
     * 关键字检索
     */
    String keyword;

    /**
     * 品牌检索
     */
    String trademark;

    /**
     * 平台属性
     */
    String[] props;

    /**
     * 排序方式
     */
    String order="1:desc";

    /**
     * 页码
     */
    Integer pageNo=1;
}

SearchRespVo

@Data
public class SearchRespVo {

    private SearchParamVo searchParamVo;

    /**
     * 品牌面包屑
     */
    private String trademarkParam;

    /**
     * 平台属性面包屑
     */
    private List<Props> propsParamList;

    /**
     * 品牌列表
     */
    private List<Trademark> trademarkList;

    /**
     * 属性列表
     */
    private List<Arrts> attrsList;

    /**
     * url参数
     */
    private String urlParam;

    /**
     * 排序
     */
    private OrderMap orderMap;

    /**
     * 商品集合; 商品数据原来是在MySQL中;需要通过上架操作给es存一份
     */
    private List<Goods> goodsList;

    /**
     * 页码
     */
    private Integer pageNo;

    /**
     * 总页码
     */
    private Long totalPages;

    @Data
    public static class Props {
        private String attrName;

        private String attrValue;

        private Long attrId;
    }

    @Data
    public static class Trademark {

        private Long tmId;

        private String tmName;

        private String tmLogoUrl;
    }

    @Data
    public static class Arrts {

        private String attrName;

        private Long attrId;

        private List<String> attrValueList;
    }

    @Data
    public static class OrderMap {

        private String type;

        private String sort;
    }

}

你可能感兴趣的:(java,开发语言)