电商项目——全文检索-ElasticSearch——第一章——中篇
电商项目——商城业务-商品上架——第二章——中篇
电商项目——商城业务-首页——第三章——中篇
电商项目——性能压测——第四章——中篇
电商项目——缓存——第五章——中篇
电商项目——商城业务-检索服务——第六章——中篇
电商项目——商城业务-异步——第七章——中篇
电商项目——商品详情——第八章——中篇
电商项目——认证服务——第九章——中篇
电商项目——购物车——第十章——中篇
电商项目——消息队列——第十一章——中篇
电商项目——订单服务——第十二章——中篇
电商项目——分布式事务——第十三章——中篇
文章目录
1:搭建页面环境
我们接下来就搭建整个商城的检索功能,同样的按照微服务自治的功能,我们的检索功能就放在mall-search下
index.html要引入依赖和标头,并且进行替换(变成linux的nginx中的文件路径/html/)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
将所有静态资源放在linux中的/mydata/nginx/html/static/search下,实现动静分离
我们要实现通过访问search.mall.com然后跳转到nginx,在到网关,最后回显到检索页面
第一步:配置域名网关映射
第二步:进行nginx的配置,配置mall.conf
第三步:进行网关配置,,前提:mall-serach要配置在nacos服务中心里面
- id: mall_host_route
uri: lb://mall-search
predicates:
- Host=search.mall.com
2:调整页面跳转
前面搭建好了页面检索环境,现在我们就来梳理一下检索逻辑
在这之前,我们要配置dev-tools的依赖和配置关闭缓存
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
spring:
thymeleaf:
cache: false
接下来我们要完成商城首页(zlj.mall.com)跳转到检索页面的跳转(search.mall.com)
第一个:打通点击三级分类下数据跳转到检索页面
第二个:点击搜素内容可以跳转到检索页面
接下来一一介绍
1:打通点击三级分类下数据跳转到检索页面
mall-search
controller
SearchController
@Controller
public class SearchController {
@GetMapping("/list.html")
public String listPage(){
return "list";
}
}
2:点击搜素内容可以跳转到检索页面
search()下的方法
window.location.href="http://search.mall.com/list.html?keyword="+keyword;
如上我们就完成了调整页面跳转
3:检索查询参数模型分析抽取
前面我们无论是选择分类,还是进行检索关键字,都打通了到search.mall.com/list.html的检索系统(mall-search),接下来,我们就分析一下检索系统检索这些商品都需要哪些检索条件。。我们是由mall-search中的controller下的SearchController类下的如下方法 @GetMapping("/list.html") public String listPage()来完成的,它要做的事情就是它要接收所有的检索条件,然后进行处理,最后进行返回;我们要写一个SearchParam类来接受所有的检索条件,还要写一个接口来进行处理,最后返回给页面list
mall-search
vo
SearchParam
如上操作流程,转化成如下代码演示
@Controller
public class SearchController {
@Autowired
MallService mallService;
@GetMapping("/list.html")
public String listPage(SearchParam searchParam){
Object result= mallService.search(param);
return "list";
}
}
我们现在就要分析,封装页面所有可呢传递过来的查询条件,查询条件有哪些???并把他们写入SearchParam类中
商品检索条件的三个入口,我们第三个入口要传递的参数是最难判断的
如下就是我们分析的有可呢会从前端传递到后端的检索条件,最终我们也希望我们的MallSearchService中的方法可以去es中通过这些检索条件,来找到我们想要的查询结果
/**
* 封装页面所有可呢传递过来的查询条件
* 自动将页面提交过来的所有请求查询参数封装成指定的对象SearchParam
* @author Mr.zhneg
* @create 2020-11-04-22:49
*/
@Data
public class SearchParam {
private String keyword;//页面传递过来的全文匹配关键字
private Long catalog3Id;//三级分类id
/**
* sort=saleCount_asc/desc
* sort=skuPrice_asc/desc
* sort=hotScore_asc/desc
*/
private String sort;//排序条件
/**
* 好多的过滤条件
* hasStock(是否有货) ,skuPrice价格区间,brandId,catalog3Id,attrs
* hasStock=0/1
* skuPrice =1_500/_500/500_
* brandId=1
*
*/
private Integer hasStock;//是否有货
private String skuPrice;//价格区间查询
private List<Long> brandId;//按照品牌进行查询,可以多选
private List<String> attrs;//按照属性进行筛选
private Integer pageNum;//页码
}
4:检索返回结果模型分析抽取
前面我们通过分析页面,将所有可呢提交的查询参数封装成了SearchParam 对象,我们调用方法把前端提交过来的参数,查询出对应的结果然后通过封装返回给页面
mall-search
vo
SearchResult
@Data
public class SearchResult{
//查询到的所有商品信息
private List<SkuEsModel> products;
/**
* 以下是分页信息
*/
private Integer pageNum;//当前页码
private Long total;//总记录数
private Integer totalPage;//总页码
private List<BrandVo> brands;//当前查询到的结果,所有涉及到的品牌
private List<CatalogVo> catalogs;//当前查询到的结果,所有涉及到的分类
private List<AttrVo> attrs;//当前查询到的结果,所有涉及到的属性
/**
*=====以上是返回给页面的所有信息
*/
@Data
public static class BrandVo{
private Long brandId;
private String brandName;
private String brandImg;
}
@Data
public static class CatalogVo{
private Long catalogId;
private String catalogName;
}
@Data
public static class AttrVo{
private Long attrId;
private String attrName;
private String attrValue;
}
}
SearchController 大致框架已经搭建完成,下章节,我们就讲如何去es中查询
public class SearchController {
@Autowired
MallSearchService mallService;
/**
* 自动将页面提交过来的所有请求查询参数封装成指定的对象
* @param searchParam
* @return
*/
@GetMapping("/list.html")
public String listPage(SearchParam searchParam, Model model){
//1:根据传递来的页面的查询参数,去es中检索商品
SearchResult result= mallService.search(searchParam);
model.addAttribute("result",result);
return "list";
}
}
5:检索DSL测试-查询部分
mall-search
- 前面我们抽取了两种数据模型,第一个是我们可呢从页面传递过来的请求参数,我们封装成SearchParam,还有一个就是我们将页面的返回结果抽取成SearchResult,我们contoller(SearchController)接受到请求就应该去调用业务逻辑里面的检索功能进行检索;而这些功能非常复杂,我们现在就要使用kibana来测试如何检索商品,为了以后可以在search(MallSearchService)方法中更好的实现业务代码
我在码云中的项目里面已经保存了,我测试检索DSL-查询部分地址如下,都在mall-search中有一个.json文件结尾的
zlj分布式代码
更难的在后面,
每次查询一个东西以后,下一次我们要查询的东西是动态变化的,它是根据我们已查到的东西聚合分析出来的(我们查到的东西的属性,值有哪些),我们下节就完成检索DSL测试-聚合部分
https://www.elastic.co/guide/index.html
6:检索DSL测试-聚合部分
我在码云中的项目里面已经保存了,我测试检索DSL-聚合部分地址如下,都在mall-search中有一个.json文件结尾的
zlj分布式代码
https://www.elastic.co/guide/index.html
7:SearchRequest构建-检索
动态的构建出查询所需要的dsl语句
https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl
@Service("MallSearchService")
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {
@Autowired
private RestHighLevelClient client;
@Override
public SearchResult search(SearchParam param) throws IOException {
//1:动态构建出查询需要的DSL语句
SearchResult result=null;
//1:准备检索请求
SearchRequest searchRequest=new SearchRequest();
searchRequest= buildSearchRequest(param);
try {
//2:执行检索请求
SearchResponse response = client.search(searchRequest, MallESConfig.COMMON_OPTIONS);
//3:分析响应数据封装成我们需要的格式
result= buildSearchResult(response);
}catch (IOException e){
e.printStackTrace();
}
return null;
}
/**
* 构建结果数据
* @param response
* @return
*/
private SearchResult buildSearchResult(SearchResponse response) {
return null;
}
/**
* 准备检索请求
* 模糊匹配。过滤(按照属性,分类,品牌,架构区间,库存),排序,分页,高亮,聚合分析
*/
private SearchRequest buildSearchRequest(SearchParam param){
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构建DSL语句
/**
* 查询:模糊匹配。过滤(按照属性,三级分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
*/
//1:构建bool -query
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//1.1 must ——模糊匹配
if (!StringUtils.isEmpty(param.getKeyword())){
boolQuery.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
}
//1.2 bool -filiter——按照三级分类id查询
if (param.getCatalog3Id()!=null){
boolQuery.filter(QueryBuilders.termsQuery("catalogId",param.getCatalog3Id()));
}
//1.2 bool -filiter——按照品牌id查询
if (param.getBrandId()!=null&¶m.getBrandId().size()>0){
boolQuery.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
}
//1.2 bool -filiter——按照属性查询
if (param.getAttrs()!=null &¶m.getAttrs().size()>0){
//不断遍历循环
//attrs=1_5寸:8寸&attrs=2_16G:8G
for (String attrStr:param.getAttrs()){
BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();
//attrs=1_5寸:8寸
String[] s = attrStr.split("_");
String attrId=s[0];//检索属性id
String[] attrValues=s[1].split(":");//这个属性的检索用的值
nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrId",attrId));
nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
//每一个都必须生成一个nested查询
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);
boolQuery.filter(nestedQuery);
}
}
//1.2 bool -filiter——按照库存查询
boolQuery.filter(QueryBuilders.termsQuery("hasStock",param.getHasStock()==1));
//1.2 bool -filiter——按照价格区间查询
if(!StringUtils.isEmpty(param.getSkuPrice())){
//1_500/_500/500_
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
String[] s = param.getSkuPrice().split("_");
if (s.length==2){
//区间
//大于前边的小于后边的
rangeQuery.gte(s[0]).lte(s[1]);
}else if (s.length==1){
if (param.getSkuPrice().startsWith("_")){
rangeQuery.lte(s[0]);
}
if (param.getSkuPrice().endsWith("_")){
rangeQuery.gte(s[0]);
}
}
}
//把以前的所有条件都拿来进行封装
sourceBuilder.query(boolQuery);
/**
* 排序,分页,高亮,
*/
/**
* 聚合分析
*/
SearchRequest searchRequest =