Day143-145.尚品汇:商品上架、ES商品检索 | 动态DSL、搜索条件处理、logstash

目录

Day10

1. 商品上架

2. 根据用户检索的条件编写 dsl 语句

3. 更新商品热度

4. 动态生成 dsl 语句

4. JUC 、SpringCloud 回顾

Day11

1. JMM、CAS、Lock回顾

2. DSL 商品检索

3. 搜索条件处理 - url拼接

4. logstash: 日志收集框架:


Day10

回顾

1.    异步编排优化:多线程
2.    首页渲染:redis ;nginx --- 静态代理
3.    全文检索:goods 索引库;新的数据类型 nested - 允许数据彼此独立的检索和查询!

1. 商品上架

本质 将mysql数据存入ES

根据用户检索的条件编写dsl语句

es 6.8.1 索引库需要自己访问控制器才能生成

es 7.8.0 项目启动会自动创建索引库,前提:

operator : "and "   默认为or

// 商品上架 --- 将数据封装到Goods,并且Goods 保存到ES中,GoodsRepository来做这件事
    @Override
    public void upperGoods(Long skuId) {
        // 创建goods,给goods 赋值
        Goods goods = new Goods();
        // 远程调用获取对应的参数
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setId(skuId);
            goods.setTitle(skuInfo.getSkuName());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            return skuInfo;
        },threadPoolExecutor);

        // skuInfo 可能是从缓存中获取数据; 延迟双删 --> 避免出现脏读,但还是有可能出现问题
        // 所以从数据库中重新获取
        CompletableFuture<Void> productCompletableFuture = CompletableFuture.runAsync(() -> {
            BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
            goods.setPrice(skuPrice.doubleValue()); //价格为BigDecimal,需要转型
            //商品上架时间:方便按照时间排序
            goods.setCreateTime(new Date());
        },threadPoolExecutor);


        //赋值品牌数据
        CompletableFuture<Void> trademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        },threadPoolExecutor);

        //赋值分类数据
        CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
        },threadPoolExecutor);

        //热度排名:hotScore默认值 0

        //赋值平台属性 (stream 在内部进行了优化和提升)
        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            List<BaseAttrInfo> attrList = this.productFeignClient.getAttrList(skuId);
            List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //skuId 对应的平台属性 -- 平台属性值只有一个
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        },threadPoolExecutor);

        // 存入ES
        CompletableFuture.allOf(
                skuInfoCompletableFuture,
                productCompletableFuture,
                trademarkCompletableFuture,
                categoryCompletableFuture,
                attrCompletableFuture
        ).join();

        goodsRepository.save(goods);
    }

2. 根据用户检索的条件编写 dsl 语句

# 分类ID
GET /goods/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category3Id": "61"
          }
        }
      ]
    }
  }
}
#根据商品名称过滤
GET /goods/_search
{
  "query": {
    "match": {
      "title": {
        "query": "小米手机",
        "operator": "and"
      }
    }
  }
}

#品牌ID 现根据分类ID过滤,再根据品牌ID过滤
GET /goods/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category3Id": "61"
          }
        },
        {
          "term": {
            "tmId": "1"
          }
        }
      ]
    }
  }
}
#平台属性 --- 数据类型是nested
GET /goods/_search
{
  "query": {
    "nested": {
      "path": "attrs",
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "attrs.attrId": "24"
              }
            },
            {
              "match": {
                "attrs.attrValue": "128G"
              }
            }
          ]
        }
      }
    }
  }
}
# 高亮
# Thymeleaf 标签th:utext 解析样式 <span style=color:red>小米<span/>
GET /goods/_search
{
  "query": {
    "match": {
      "title": "手机"
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    },
    "pre_tags": [
      "<span style=color:red>"
    ],
    "post_tags": [
      "</span>"
    ]
  }
}
#聚合
#目的:展示数据,进行过滤
GET /goods/_search
{
  "query": {
    "match": {
      "category3Id": "61"
    }
  },
  "aggs": {
    "tmIdAgg": {
      "terms": {
        "field": "tmId",
        "size": 10
      }
    }
  }
}
#品牌聚合+平台属性聚合 方便取数据
GET /goods/_search
{
  "query": {
    "match": {
      "category3Id": "61"
    }
  },
  "aggs": {
    "tmIdAgg": {
      "terms": {
        "field": "tmId",
        "size": 10
      },
      "aggs": {
        "tmNameAgg": {
          "terms": {
            "field": "tmName",
            "size": 10
          }
        },
        "tmLogoUrlAgg":{
          "terms": {
            "field": "tmLogoUrl",
            "size": 10
          }
        }
      }
    },
    "attrAgg":{
      "nested": {
        "path": "attrs"
      },
      "aggs": {
        "attrIdAgg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 10
          },
          "aggs": {
            "attrNameAgg": {
              "terms": {
                "field": "attrs.attrName",
                "size": 10
              },
              "aggs": {
                "attValueAgg": {
                  "terms": {
                    "field": "attrs.attrValue",
                    "size": 10
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

3. 更新商品热度

考虑缓存穿透 缓存击穿 缓存雪崩,存储时的类型,value

    //更新ES商品热度
    @Override
    public void incrHotScore(Long skuId) {
        //被访问的次数 -- redis 考虑三个问题+数据类型
        //string incrby key 100 ; 
        //ZSet zincrby hotScore 100 21
        //详情string 热度zset  秒杀list
        String hotKey = "hotScore";
        Double count = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        // 10 的倍数存入ES,减少IO
        if(count%10==0){
            // 更新ES
            Optional<Goods> optional = goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            goodsRepository.save(goods);
        }
    }

在商品详情item 远程调用ListApiController控制器方法即可 

4. 动态生成 dsl 语句

//商品检索
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        //1.生成dsl语句
        //2.执行dsl
        //3.将执行之后的结果进行封装
        //声明一个查询请求对象
        SearchRequest searchRequest = this.buildDsl(searchParam);
        //执行dsl语句
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //将执行之后的结果进行封装 SearchResponseVo
        SearchResponseVo searchResponseVo = this.parseResult(searchResponse);
        /*
            private List<SearchResponseTmVo> trademarkList;
            private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
            private List<Goods> goodsList = new ArrayList<>();
            private Long total;
             ------ 以上四个方法在parseResult中赋值
            private Integer pageSize;
            private Integer pageNo;
            private Long totalPages;
         */
        //设置每页默认显示条数:3
        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());
        /*Long totalPages = searchResponseVo.getTotal()%searchParam.getPageSize()==0?
                searchResponseVo.getTotal()%searchParam.getPageSize():
                searchResponseVo.getTotal()%searchParam.getPageSize()+1;*/
        // from = (pageNo-1)*PageSize
        Long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);

        return searchResponseVo;
    }


    //动态生成dsl语句
    private SearchRequest buildDsl(SearchParam searchParam) {

        // 构建查询器 {}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //{bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 判断男用户是否根据三级分类id进行检索
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //{filter term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        //根据关键词检索
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.
                    matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
        }

        //根据平台属性值进行过滤、
        //先获取到用户点击的数据
        String[] props = searchParam.getProps();
        if(props!=null && props.length>0){
            for (String prop : props) {
                //  第一次遍历 prop=24:256G:机身内存  第二次遍历 prop=106:安卓手机:手机一级
                String[] split = prop.split(":");
                // 声明中间层的bool
                BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
                // 声明内层的bool
                BoolQueryBuilder innerBoolBuilder = QueryBuilders.boolQuery();
                // 设置内层
                innerBoolBuilder.must(QueryBuilders.matchQuery("attrs.attrId",split[0]));
                innerBoolBuilder.must(QueryBuilders.matchQuery("attrs.attrValue",split[1]));
                // 设置中间层
                boolBuilder.must(QueryBuilders.nestedQuery("attrs",innerBoolBuilder, ScoreMode.None));
                boolQueryBuilder.filter(boolBuilder);
                //
            }
        }

        //根据品牌Id 进行检索: trademark=3:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            //分割
            String[] split = trademark.split(":");
            //判断是否根据品牌检索
            if(split!=null && split.length==2){
                //{filter term}
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        //分页,高亮,排序

        //{query}
        searchSourceBuilder.query(boolQueryBuilder);
        //{query -- bool}

        return null;
    }

    private SearchResponseVo parseResult(SearchResponse searchResponse) {
        return null;
    }

4. JUC 、SpringCloud 回顾

下午:JUC

1. 线程 - 进程 (java默认两个线程 main、gc)

每一个程序都有一个进程,操作系统动态执行的基本单元;

一个进程中可以包含若干个线程;线程是资源调度的最小单位

2. 并发 - 并行

并行:同一时间多个线程在执行,同一时刻多个线程在访问同一个资源,  (烧水泡面)

并发:同一时间多个线程在做同一件事 (秒杀、春运抢票)

3. wait - sleep

wait 释放锁,sleep 不释放锁

wait是Object的方法,sleep是Thread

4. sync

同步方法锁  this

静态同步方法锁 Class.class

同步代码块 ()中对象

5. 线程间的通信

wait notify notifyAll

线程操作资源类,高内聚低耦合

判断 干活 通知

防止线程虚假唤醒 while

6. 定制化通信  JUC.Lock (一个线程打印五次,一个十次)

lock.lock;lock.tryLock;lock.unlock();

Object Condition

await

signal() signalAll();

7. Lock 和Sync区别

共同点:可重入、独占锁

不同点:Lock 程序员控制,不容易发生死锁;Sync jvm控制,容易发生死锁

recurrentLock:如何理解可重入、公平锁、尝试获取锁

重入前提:需要同一个线程否则重入不了

8. 创建线程的集中方式:Thread、Runnable、Callable、Pool

Runnable Callable区别:Callable可获取返回值、异常,重写call()

        需要借助FutureTask 未来任务:

        get():获取当前线程执行结果

        isDone():判断这个线程是否执行完成

9. 阻塞队列:控制线程什么时候挂起,什么时候运行

        阻塞队列满的时候,再向队列中添加数据,则会发生阻塞 put

        阻塞队列空的时候,再向队列中获取数据,则会发生阻塞 take

SpringCloud

1. 分布式:n个计算机组成的系统

2. 集群:多台服务器集中在一起,实现同一个业务

3. RPC 远程过程调用:需要经过网络传输;需要协议http、https,所以需要实现序列化

 ES 使用 net

ESC 弹性云:可扩容的服务器

spring组件:别说阿里巴巴的

3. SpringBoot核心注解:SpringBootApplication:自动注入和自动装配

SpringBootCOnfiguration、EnableAutoCOnfiguration

Resource:Javax的注解 默认按照ByName注入

Autoware:Spring 的注解 默认按照ByType注入

SpringCloud组件

Eureka 注册中心 服务治理 -- 了解,以后不会用
Ribbon 负载均衡Load Balancer(LB)  默认轮询

Ribbon VS Nginx 负载均衡的区别(Gateway)

Nginx 是服务器负载均衡,接收客户请求,可以直接配置服务器地址列表

Ribbon 是本地负载均衡,需要从注册中心获取到服务名,本质:找的接口 /*/product/**

请求 -》 nginx  -》多个微服务 (根据接口去找服务!!)

Gateway网关底层整合了Ribbon,配置是一样的

Feign 和 OpenFeign 的区别

Feign 是一个SpringCloud中一个轻量级的RestFul 的http客户端,能够进行远程调用并支持负载均衡

OpenFeign 在原生基础上支持了SpringMVC注解,通过动态代理的方式产生实现类,实现负载均衡。

Hystrix断路器 保护后台微服务,类似Sentinel

Day11

1. JMM、CAS、Lock回顾

JMM:用于定义数据读写的规则保证同一份代码在不同的环境中,能够正常运行。

每个线程都有一个主内存、工作内存,需要先将主内存的资源读取到工作内存进行操作。

主内存、工作内存。

三个特性:

原子性:int i = 1 原子性,i++ 非原子性;锁保证

可见性:在多线程中要保证这个变量可见 volatile,sync,finall

有序性:多线程中执行顺序有序 sync,valatile

valatile:可见性、有序性不具有原子性;修饰的资源多线程可见;弱同步机制

有序性原理:禁止指令重排,有个内存屏障,执行时能保证有序

CAS:比较交换 campareAndSwapInt

cas原理:CAS(this,便宜量,预期值,修改值) 

底层 do {} while()

缺点:开销大、ABA问题、不能保证代码块具有原子性

AtomicInteger 具有原子性操作的类

Lock 底层原理

lock 接口有ReentrantLock实现类:lock() unlock();

AQS:AbstractQueuedSynchronizer 抽象队列同步器

FIFO --> 队列 队列中存储很多线程,这些线程共同抢占一个资源(volatile 修饰的 state)

上锁本质:state+1 解锁本质:state-1

//	上锁 本质 获取state +1
final void lock() {
	//	比较交换
	if (compareAndSetState(0, 1))
		//	设置用户线程
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}

final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	//	获取资源
	int c = getState();
	if (c == 0) {
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	else if (current == getExclusiveOwnerThread()) {  // 可重入
		//	如果可重入在原理基础上+1
		int nextc = c + acquires;
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

//	解锁本质:state - 1
public void unlock() {
	sync.release(1);
}
protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}

2. DSL 商品检索

聚合的目的: 去重显示、根据聚合的数据进行过滤

    //商品检索
    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        //1.生成dsl语句
        //2.执行dsl
        //3.将执行之后的结果进行封装
        //声明一个查询请求对象
        SearchRequest searchRequest = this.buildDsl(searchParam);
        //执行dsl语句
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //将执行之后的结果进行封装 SearchResponseVo
        SearchResponseVo searchResponseVo = this.parseResult(searchResponse);
        /*
            private List<SearchResponseTmVo> trademarkList;
            private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
            private List<Goods> goodsList = new ArrayList<>();
            private Long total;
             ------ 以上四个方法在parseResult中赋值
            private Integer pageSize;
            private Integer pageNo;
            private Long totalPages;
         */
        //设置每页默认显示条数:3
        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());
        /*Long totalPages = searchResponseVo.getTotal()%searchParam.getPageSize()==0?
                searchResponseVo.getTotal()%searchParam.getPageSize():
                searchResponseVo.getTotal()%searchParam.getPageSize()+1;*/
        // from = (pageNo-1)*PageSize
        Long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);

        return searchResponseVo;
    }

    //动态生成dsl语句
    private SearchRequest buildDsl(SearchParam searchParam) {

        // 构建查询器 {}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //{bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 判断男用户是否根据三级分类id进行检索
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //{filter term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        //根据关键词检索
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
            //高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title").preTags("<span style=color:red>").postTags("</span>");
            //HighlightBuilder highlightBuilder = searchSourceBuilder.highlighter()
            //        .field("title").preTags("<span style=color:red>").postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        //根据平台属性值进行过滤、
        //先获取到用户点击的数据
        String[] props = searchParam.getProps();
        if(props!=null && props.length>0){
            for (String prop : props) {
                //  第一次遍历 prop=24:256G:机身内存  第二次遍历 prop=106:安卓手机:手机一级
                String[] split = prop.split(":");
                // 声明中间层的bool
                BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
                // 声明内层的bool
                BoolQueryBuilder innerBoolBuilder = QueryBuilders.boolQuery();
                // 设置内层
                innerBoolBuilder.must(QueryBuilders.matchQuery("attrs.attrId",split[0]));
                innerBoolBuilder.must(QueryBuilders.matchQuery("attrs.attrValue",split[1]));
                // 设置中间层
                boolBuilder.must(QueryBuilders.nestedQuery("attrs",innerBoolBuilder, ScoreMode.None));
                boolQueryBuilder.filter(boolBuilder);
                //
            }
        }

        //根据品牌Id 进行检索: trademark=3:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            //分割
            String[] split = trademark.split(":");
            //判断是否根据品牌检索
            if(split!=null && split.length==2){
                //{filter term}
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }
        //{query}
        searchSourceBuilder.query(boolQueryBuilder);

        //分页
        //from 当前的起始条数
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        //默认展示三条
        searchSourceBuilder.size(searchParam.getPageSize());

        //排序:规则表圛,是根据页面传递的数据决定的
        //order=1:asc order2:desc
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            String[] split = order.split(":");
            //需要知道排序的字段与规则 1=hostScore 2=price
            String field = "";
            if(split!=null && split.length==2){
                switch (split[0]){
                    case "1":
                        field = "hotScore";
                        //searchSourceBuilder.sort("hotScore","asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
                        break;
                    case "2":
                        field = "price";
                        //searchSourceBuilder.sort("price","asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
                        break;
                }
                searchSourceBuilder.sort(field,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
            }
        } else {
            //默认热度 倒序
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);
        }

        //聚合: 去重,显示,提供检索条件
        //第一部分:品牌  注意结构!并列关系!
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //第二部分:平台属性 -- 数据类型 nested
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        //声明一个请求对象 GET /goods/_search 在哪个索引库查询
        SearchRequest searchRequest = new SearchRequest("goods");
        //将dsl语句赋值给查询对象
        searchRequest.source(searchSourceBuilder);
        //看到dsl语句
        System.out.println("DSL:\t"+searchSourceBuilder.toString());
        //哪些field需要展示数据,哪些不需要展示数据
        searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price","createTime"},null);
        return searchRequest;
    }

    //进行结果封装 返回数据
    private SearchResponseVo parseResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();
        //设置总记录数
        //private Long total;
        searchResponseVo.setTotal(hits.getTotalHits().value);

        //设置商品的集合
        //private List<Goods> goodsList = new ArrayList<>();
        ArrayList<Goods> goodsList = new ArrayList<>();
        SearchHit[] subHits = hits.getHits();
        if(subHits!=null && subHits.length>0){
            for (SearchHit subHit : subHits) {
                //获取goods字符串
                String sourceAsString = subHit.getSourceAsString();
                //将其转换为goods
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //判断用户是否根据关键词进行检索,如果是则获取高亮字段
                if(subHit.getHighlightFields().get("title")!=null){
                    Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                    goods.setTitle(title.toString());
                }
                goodsList.add(goods);
            }
        }
        searchResponseVo.setGoodsList(goodsList);

        //设置品牌集合
        //private List<SearchResponseTmVo> trademarkList;
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        // 应获取桶里的数据! 多态 根据key类型决定ParsedLongTerms
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //循环遍历时获取品牌ID给searchResponseVo,再将对象添加到集合中
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //获取品牌ID
            String tmId = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(tmId));

            //获取品牌Name
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //获取品牌LogoUrl
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //平台属性值: 数据类型nested
        //private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        //循环遍历获取平台属性ID、平台属性名、平台属性值集合
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            //声明一个平台属性对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //获取平台属性ID
            String attrId = bucket.getKeyAsString();
            searchResponseAttrVo.setAttrId(Long.parseLong(attrId));

            //获取平台属性名
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //获取平台属性值集合
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");

            /*List<? extends Terms.Bucket> buckets = attrValueAgg.getBuckets();
            ArrayList<String> list = new ArrayList<>();
            buckets.forEach(value->{
                String ValueName = value.getKeyAsString();
                list.add(ValueName);
            });*/

            List<String> valueList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueList);

            return searchResponseAttrVo;
        }).collect(Collectors.toList());

        searchResponseVo.setAttrsList(attrsList);

        return searchResponseVo;
    }

}

3. 搜索条件处理 - url拼接

网关 -》 域名解析 Gateway-》OpenFeign service-inem

//商品检索
@Controller
public class ListController {

    @Qualifier("com.atguigu.gmall.list.client.ListFeignClient")
    @Autowired
    private ListFeignClient listFeignClient;

    //通过浏览器会将用户输入的检索条件,直接封装到这个实体类!  --- 为什么能封装进去? 因为参数名一样
    @GetMapping("list.html")
    public String search(SearchParam searchParam, Model model) {
        //  urlParam searchParam trademarkParam propsParamList orderMap 需要自己组装!
        //  trademarkList attrsList goodsList pageNo totalPages  这些数据都是实体类的属性 SearchResponseVo
        //  面包屑:trademarkParam propsParamList
        Result<Map> result = listFeignClient.list(searchParam);

        // urlParam 表示点击平台拼接属性值之前的url路径
        String urlParam = this.makeUrlParam(searchParam);
        //品牌面包屑
        String trademarkParam = this.makeTradeMarkParam(searchParam.getTrademark());
        //平台属性面包屑:
        List<SearchAttr> searchAttrList = this.makeSearchAttr(searchParam.getProps());
        //List<Map> searchAttrList = this.makeSearchAttr(searchParam.getProps());

        //排序规则
        Map<String,Object> orderMap = this.makeOrderMap(searchParam.getOrder());

        //map 可以充当对象,poenFeign远程调用时
        model.addAllAttributes(result.getData());
        model.addAttribute("searchParam",searchParam);
        model.addAttribute("urlParam",urlParam);
        model.addAttribute("trademarkParam",trademarkParam);
        model.addAttribute("propsParamList",searchAttrList);
        model.addAttribute("orderMap",orderMap);

        return "list/index";
    }
    //制作排序
    private Map<String, Object> makeOrderMap(String order) {
        //  order=1:asc  order=1:desc   order=2:asc order=2:desc
        HashMap<String, Object> map = new HashMap<>();
        if(!StringUtils.isEmpty(order)){
            String[] split = order.split(":");
            //用户点击了排序
            if(split!=null && split.length==2){
                //按照哪种方式排序
                map.put("type",split[0]);
                //排序规则 asc 或 desc
                map.put("sort",split[1]);
            }
        }else {
            map.put("type",1);
            //排序规则 asc 或 desc
            map.put("sort","desc");
        }
        return map;
    }

    //平台属性面包屑集合
    private List<SearchAttr> makeSearchAttr(String[] props) {
        ArrayList<SearchAttr> searchAttrList = new ArrayList<>();
        //  props=24:256G:机身内存&props=23:8G:运行内存
        if(props!=null && props.length>0){
            for (String prop : props) {
                String[] split = prop.split(":");
                if(split!=null && split.length==3){
                    SearchAttr searchAttr = new SearchAttr();
                    searchAttr.setAttrId(Long.parseLong(split[0]));
                    searchAttr.setAttrValue(split[1]);
                    searchAttr.setAttrName(split[2]);
                    searchAttrList.add(searchAttr);
                }
            }
            return searchAttrList;
        }
        return null;
    }

    //品牌面包屑
    private String makeTradeMarkParam(String trademark) {
        // trademark=1:小米
        if(!StringUtils.isEmpty(trademark)){
            String[] split = trademark.split(":");
            if(split!=null && split.length==2){
                return "品牌:"+trademark.split(":")[1];
            }
        }
        return null;
    }

    //记录用户通过哪些条件进行了检索
    private String makeUrlParam(SearchParam searchParam) {
        // 字符串拼接
        StringBuilder stringBuilder = new StringBuilder();
        // 判断用户根据哪些条件进行了检索
        //全文检索
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            stringBuilder.append("keyword=").append(searchParam.getKeyword());
        }
        //分类ID
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())){
            stringBuilder.append("category3Id=").append(searchParam.getCategory3Id());
        }
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())){
            stringBuilder.append("category2Id=").append(searchParam.getCategory2Id());
        }
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())){
            stringBuilder.append("category1Id=").append(searchParam.getCategory1Id());
        }
        //品牌
        if(!StringUtils.isEmpty(searchParam.getTrademark())){
            if(stringBuilder.length()>0){
                stringBuilder.append("&trademark=").append(searchParam.getTrademark());
            }
        }
        //平台属性值
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            // 循环遍历
            for (String prop : props) {
                if(stringBuilder.length()>0){
                    stringBuilder.append("&props=").append(prop);
                }
            }
        }
        System.out.println("list.html?"+stringBuilder.toString());
        return "list.html?"+stringBuilder.toString();
    }
}

4. logstash: 日志收集框架:


        日志需要有固定存储!
    ELK:    
        安装:
        添加配置文件:
        添加logstash 的依赖jar包。

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值