PUT gemme
{
"mappings": {
"properties": {
"productId": { //商品ID
"type": "long"
},
"price": { //商品价格
"type": "keyword"
},
"discountPrice": { //商品折扣价格
"type": "keyword"
},
"productName": { //商品名称
"type": "text",
"analyzer": "ik_smart" //按照ik_smart进行分词
},
"productImg": { //商品图片
"type": "text",
"index": false, //不允许被查询
"doc_values": false //不允许被聚合
},
"brandId": { //品牌ID
"type": "long"
},
"brandName": { //品牌名称
"type": "keyword"
},
"oneCategoryId": { //一级分类ID
"type": "long"
},
"oneCategoryName": { //一级分类名称
"type": "keyword"
},
"twoCategoryId": { //二级分类ID
"type": "long"
},
"twoCategoryName": { //一级分类名称
"type": "keyword"
},
"lockCnt": { //热销
"type": "long"
},
"publishStatus": { //状态
"type": "long"
},
"productEffect": { //功效
"type": "nested", //防止在es库中被扁平处理
"properties": {
"productEffectId": {
"type": "long"
},
"productEffectName": {
"type": "keyword"
}
}
},
"skin": { //肤质
"type": "nested",
"properties": {
"skinId": {
"type": "long"
},
"skinName": {
"type": "keyword"
}
}
}
}
}
}
@Data
public class ProductEsVo {
//商品ID
private Long productId;
//商品名称
private String productName;
//商品价格
private BigDecimal price;
//商品打折后的价格
private BigDecimal discountPrice;
// 商品主图
private String productImg;
//品牌ID
private Long brandId;
//品牌名称
private String brandName;
//一级分类Id
private Long oneCategoryId;
//一级分类名称
private String oneCategoryName;
//二级分类Id
private Long twoCategoryId;
//二级分类名称
private String twoCategoryName;
//出售量
private Long lockCnt;
//商品状态
private Long publishStatus;
//商品功效
private List<ProductEffect> productEffect;
//肤质
private List<Skin> skin;
@Data
public static class ProductEffect{
//功效ID
private Long productEffectId;
//功效名称
private String productEffectName;
}
@Data
public static class Skin{
//肤质ID
private Long skinId;
//肤质名称
private String skinName;
}
}
List<ShopProductInfo> shopList=this.shopProductList();
List<ProductEsVo> esList=shopList.stream().map(pro->{
ProductEsVo esvo=new ProductEsVo();
esvo.setProductId(pro.getId());
esvo.setProductName(pro.getProductName());
esvo.setPrice(pro.getProductPrice());
esvo.setBrandId(pro.getBrandId());
esvo.setOneCategoryId(pro.getOneCategoryId());
esvo.setTwoCategoryId(pro.getTwoCategoryId());
esvo.setLockCnt((long) shopWarehouseProductService.getWarehouseProductLockCnt(pro.getId())!=0?(long) shopWarehouseProductService.getWarehouseProductLockCnt(pro.getId()):0);
esvo.setBrandName(this.shopBrandString(pro.getBrandId()));
esvo.setPublishStatus(Long.parseLong(publishStatus.toString()));
esvo.setProductImg(shopProductPictureService.getPrcUrl(pro.getId()));
esvo.setOneCategoryName(this.shopCategoryName(pro.getOneCategoryId()));
esvo.setTwoCategoryName(this.shopCategoryName(pro.getTwoCategoryId()));
String skuList = shopSkuService.skuList(pro.getId());
if(skuList!=null && skuList.length()>0){
List<ShopAttributeResponseVo> shopAttributeResponseVos = JSON.parseArray(skuList, ShopAttributeResponseVo.class);
List<List<ShopAttributeResponseVo.AttributeChildren>> listChildren = shopAttributeResponseVos.stream().filter(item -> {
return item.getKey().equals(shopAttributeService.getAttributeId(pro.getOneCategoryId()).toString());
}).map(itemMap -> {
return itemMap.getChildren();
}).collect(Collectors.toList());
if(listChildren.size()>0 && listChildren!=null){
List<ProductEsVo.ProductEffect> effectList = listChildren.get(0).stream().map(childrenItem -> {
ProductEsVo.ProductEffect effect = new ProductEsVo.ProductEffect();
effect.setProductEffectId(Long.parseLong(childrenItem.getId()));
effect.setProductEffectName(childrenItem.getAttributeName());
return effect;
}).collect(Collectors.toList());
esvo.setProductEffect(effectList);
}
}
return esvo;
}
).collect(Collectors.toList());
return shopSearchFeign.saveSearch(esList); //调用elasticSearch服务的存放接口
@FeignClient(contextId = "shopSearchFeign", value = "gemme-search”)
public interface ShopSearchFeign {
@PostMapping("/search/info")
public R saveSearch(@RequestBody List<ProductEsVo> list);
}
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
首先注意:导入的elasticSearch版本号一定要和安装liunx中elasticSearch版本一致 。如果springboot自带elasticSearch版本号跟导入的jar版本号不一致,需要手动修改springBoot elasticSearch版本号
spring.elasticsearch.rest.uris: ip:9200
(这里是单个elasticSearch,可以根据自己业务配置集群)
@Configuration
public class SearchConfig {
@Bean
public RestHighLevelClient getRestHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("ip", 9200, "http")));
return client;
}
}
public class EsConstant {
public static final String GEMME_INDEX = "gemme"; //elasticSearch名称
public static final int PASE_SIZE = 10;
}
@RestController
@RequestMapping("/search")
public class SearchController {
private final SearchService searchService;
@PostMapping("/info")
public R saveSearch(@RequestBody List<ProductEsVo> list) throws IOException {
return R.ok(searchService.saveSearch(list));
}
}
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private RestHighLevelClient getRestHighLevelClient;
@Override //这里是批量插入
public boolean saveSearch(List<ProductEsVo> list) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
for (ProductEsVo productEsVo : list) {
IndexRequest indexRequest = new IndexRequest(EsConstant.GEMME_INDEX);
indexRequest.id(productEsVo.getProductId().toString());
indexRequest.source(JSON.toJSONString(productEsVo), XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulk = getRestHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return bulk.hasFailures();
}
}
@Data
public class SearchVo {
//搜索框内容
private String keyWork;
//品牌ID
private Long brandId;
//商品分类二级ID
private Long twoCategoryId;
//商品价格
private String price; //接收数据结构: 1-100 1- -1
/**
* 排序条件
* sort=price-asc/desc 按照价格进行排序
* sort=lockCnt-asc/desc 按照销售量进行排序
*/
private String sort;
//功效筛选
private String productEffectId; //接收数据结构:功效ID1-功效ID2
//页码
private Integer pageNumber = 1;
@Data
public class SearchRespon {
//search服务中的数据
private List<ProductEsVo> productEsVoList;
/**
* 分页信息
*/
private Integer pageNnmber;//当前页码
private Long total;//总记录数
private Integer totalPages;//总页码
//品牌
private List<BrandVo> brandVoList;
//功效
private List<ResponseEffect> responseEffectList;
@Data
public static class BrandVo{
//品牌ID
private Long brandId;
//品牌名称
private String brandName;
}
@Data
public static class ResponseEffect{
//功效ID
private Long productEffectId;
//功效名称
private String productEffectName;
}
}
/**
* 查询数据
*/
@GetMapping("/searchKeywork")
public R searchKeywork(SearchVo searchVo){
return R.ok(searchService.searchKey(searchVo));
}
@Override
public SearchRespon searchKey(SearchVo searchVo) {
SearchRespon request=null;
SearchRequest searchRequest=querySearch(searchVo);
try {
//获得返回的数据
SearchResponse response = getRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//返回封装数据格式
request= querySearchResponse(response,searchVo);
} catch (IOException e) {
e.printStackTrace();
}
return request;
}
private SearchRequest querySearch(SearchVo searchVo) {
//构建搜索条件
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
//查询条件
//根据搜索查询bool
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//按照搜索框信息查询
if(!StringUtils.isEmpty(searchVo.getKeyWork())){
boolQueryBuilder.must(QueryBuilders.matchQuery("productName",searchVo.getKeyWork()));
}
//按照品牌ID查询
if(searchVo.getBrandId()!=null){
boolQueryBuilder.filter(QueryBuilders.termQuery("brandId",searchVo.getBrandId()));
}
//按照二级分类进行查询
if(searchVo.getTwoCategoryId()!=null){
boolQueryBuilder.filter(QueryBuilders.termQuery("twoCategoryId",searchVo.getTwoCategoryId()));
}
//按照价格进行筛选 1-1000 1- -1
if(!StringUtils.isEmpty(searchVo.getPrice())){
RangeQueryBuilder priceRange = QueryBuilders.rangeQuery("price");
String[] priceSplit=searchVo.getPrice().split("-");
if(priceSplit.length ==2){
priceRange.gte(priceSplit[0]).lte(priceSplit[1]);
}else if(priceSplit.length ==1){
if(searchVo.getPrice().startsWith("-")){
priceRange.lte(priceSplit[0]);
}
if (searchVo.getPrice().endsWith("-")){
priceRange.gte(priceSplit[0]);
}
}
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price"));
}
//通过功效查询 1-2-3
if(!StringUtils.isEmpty(searchVo.getProductEffectId())){
String[] pro=searchVo.getProductEffectId().split("-");
for (int i = 0; i < pro.length; i++) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("productEffect.productEffectId",pro[i]));
// 扁平化处理
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("productEffect",boolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
}
}
searchSourceBuilder.query(boolQueryBuilder);
//排序 price-desc /asc lockCnt-desc/asc
if(!StringUtils.isEmpty(searchVo.getSort())){
String[] stringSort=searchVo.getSort().split("-");
SortOrder order=stringSort[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
searchSourceBuilder.sort(stringSort[0],order);
}
//分页
searchSourceBuilder.from((searchVo.getPageNumber()-1)*EsConstant.PASE_SIZE);
searchSourceBuilder.size(EsConstant.PASE_SIZE);
//构建聚合 (品牌,功效)
TermsAggregationBuilder brand_agg= AggregationBuilders.terms("brand_agg");
brand_agg.field("brandId").size(50);
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(50));
searchSourceBuilder.aggregation(brand_agg);
NestedAggregationBuilder effect_agg = AggregationBuilders.nested("effect_agg","productEffect");
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("product_effectId_agg").field("productEffect.productEffectId").size(20);
termsAggregationBuilder.subAggregation(AggregationBuilders.terms("product_effectName_agg").field("productEffect.productEffectName").size(20));
effect_agg.subAggregation(termsAggregationBuilder);
searchSourceBuilder.aggregation(effect_agg);
SearchRequest searchRequest=new SearchRequest(new String[]{EsConstant.GEMME_INDEX},searchSourceBuilder);
return searchRequest;
}
private SearchRespon querySearchResponse(SearchResponse response,SearchVo searchVo) {
SearchRespon searchRespon = new SearchRespon();
SearchHits hits = response.getHits();
List<ProductEsVo> voList=new ArrayList<>();
//获得商品所有基础信息
if(hits.getHits() != null && hits.getHits().length>0){
for (SearchHit hit : hits.getHits()) {
ProductEsVo esVo = JSON.parseObject(hit.getSourceAsString(), ProductEsVo.class);
voList.add(esVo);
}
}
searchRespon.setProductEsVoList(voList);
//获取品牌数据
ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
List<SearchRespon.BrandVo> brandVoList = brand_agg.getBuckets().stream().map(item -> {
SearchRespon.BrandVo brandVo = new SearchRespon.BrandVo();
brandVo.setBrandId(Long.parseLong(item.getKeyAsString()));
ParsedStringTerms brand_name_agg = item.getAggregations().get("brand_name_agg");
brandVo.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
return brandVo;
}).collect(Collectors.toList());
searchRespon.setBrandVoList(brandVoList);
//获得功效数据
ParsedNested effect_agg = response.getAggregations().get("effect_agg");
ParsedLongTerms e_agg = effect_agg.getAggregations().get("product_effectId_agg");
List<SearchRespon.ResponseEffect> effectList = e_agg.getBuckets().stream().map(item -> {
SearchRespon.ResponseEffect responseEffect = new SearchRespon.ResponseEffect();
responseEffect.setProductEffectId(((Terms.Bucket) item).getKeyAsNumber().longValue());
ParsedStringTerms product_effectName_agg = item.getAggregations().get("product_effectName_agg");
responseEffect.setProductEffectName(product_effectName_agg.getBuckets().get(0).getKeyAsString());
return responseEffect;
}).collect(Collectors.toList());
searchRespon.setResponseEffectList(effectList);
//当前页码
searchRespon.setPageNnmber(searchVo.getPageNumber());
//总记录数
long total = hits.getTotalHits().value;
searchRespon.setTotal(total);
//总页码
searchRespon.setTotalPages((int) (total%EsConstant.PASE_SIZE==0?total/EsConstant.PASE_SIZE:(total/EsConstant.PASE_SIZE+1)));
return searchRespon;
}
{
"code": 0,
"msg": null,
"data": {
"productEsVoList": [
{
"productId": 1,
"productName": "法国Dior/迪奥新款黑管瘾诱超模漆光唇釉口红唇膏740保湿持久正品",
"price": 279.0,
"discountPrice": null,
"productImg": null,
"brandId": 112359881173,
"brandName": "Dior/迪奥",
"oneCategoryId": 40,
"oneCategoryName": "口红",
"twoCategoryId": 42,
"twoCategoryName": "迪奥",
"lockCnt": 32,
"publishStatus": 0,
"productEffect": [
{
"productEffectId": 1278521450449358849,
"productEffectName": "美白"
},
{
"productEffectId": 1278521486050611201,
"productEffectName": "保湿"
}
],
"skin": null
},
{
"productId": 2,
"productName": "正品YSL圣罗兰细管纯口红小金条持久哑光21复古红",
"price": 123.0,
"discountPrice": null,
"productImg": null,
"brandId": 112359881174,
"brandName": "YSL/圣罗兰",
"oneCategoryId": 40,
"oneCategoryName": "口红",
"twoCategoryId": 43,
"twoCategoryName": "YSL",
"lockCnt": 0,
"publishStatus": 0,
"productEffect": [
{
"productEffectId": 1278521450449358849,
"productEffectName": "美白"
},
{
"productEffectId": 1278521486050611201,
"productEffectName": "保湿"
}
],
"skin": null
}
],
"pageNnmber": 1,
"total": 2,
"totalPages": 1,
"brandVoList": [
{
"brandId": 112359881173,
"brandName": "Dior/迪奥"
},
{
"brandId": 112359881174,
"brandName": "YSL/圣罗兰"
}
],
"responseEffectList": [
{
"productEffectId": 1278521450449358849,
"productEffectName": "美白"
},
{
"productEffectId": 1278521486050611201,
"productEffectName": "保湿"
}
]
}
}
1.在querySearch中打印System.out.println(searchSourceBuilder.toString());,
将打印结构放入Kibana中进行测试
GET gemme/_search
{
"from": 0,
"size": 10,
"query": {
"bool": {
"must": [
{
"match": {
"productName": {
"query": "正品YSL圣罗兰",
"operator": "OR",
"prefix_length": 0,
"max_expansions": 50,
"fuzzy_transpositions": true,
"lenient": false,
"zero_terms_query": "NONE",
"auto_generate_synonyms_phrase_query": true,
"boost": 1
}
}
}
],
"filter": [
{
"nested": {
"query": {
"bool": {
"must": [
{
"term": {
"productEffect.productEffectId": {
"value": "1278521450449358849",
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
"path": "productEffect",
"ignore_unmapped": false,
"score_mode": "none",
"boost": 1
}
},
{
"nested": {
"query": {
"bool": {
"must": [
{
"term": {
"productEffect.productEffectId": {
"value": "1278521486050611201",
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
"path": "productEffect",
"ignore_unmapped": false,
"score_mode": "none",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
"aggregations": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 50,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_key": "asc"
}
]
},
"aggregations": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 50,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_key": "asc"
}
]
}
}
}
},
"effect_agg": {
"nested": {
"path": "productEffect"
},
"aggregations": {
"product_effectId_agg": {
"terms": {
"field": "productEffect.productEffectId",
"size": 20,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_key": "asc"
}
]
},
"aggregations": {
"product_effectName_agg": {
"terms": {
"field": "productEffect.productEffectName",
"size": 20,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_key": "asc"
}
]
}
}
}
}
}
}
}
}