检索服务
这里有关于检索的功能我们基于微服务架构应该抽出来单独作为一个服务运行,所以新建gulimall-search服务并注册到nacos中,以便网关服务可以监控到检索服务并将检索的请求转发给检索服务。
搭建页面环境
我们需要加载检索服务的index页面,那么先把这个页面放入templates包下,然后我们项目是基于动静分离进行资源配置的,所以需要把index页面中需要用到的静态资源放到nginx中。
动静分离资源配置
-
给gulimall-search服务加入Thymeleaf(模板引擎)依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
将
分布式高级篇/代码/html/搜索页
路径下的 index.html首页复制到 gulimall-search服务src/main/resources/templates
路径下 -
将
分布式高级篇/代码/html/搜索页
路径下的所有其他静态资源复制到 nginx 的static(静态资源)目录下,这里因为检索业务为一个独立的服务,所以我们新建一个包用于存放其静态资源。xjx@xjx search % pwd /Users/hgw/Documents/Software/mydata/nginx/html/static/search xjx@xjx search % ll total 0 drwxrwxr-x@ 4 hgw staff 128B 3 22 2020 css drwxrwxr-x@ 12 hgw staff 384B 3 22 2020 font drwxrwxr-x@ 68 hgw staff 2.1K 3 22 2020 image drwxrwxr-x@ 86 hgw staff 2.7K 3 22 2020 img drwxrwxr-x@ 4 hgw staff 128B 3 22 2020 js drwxrwxr-x@ 3 hgw staff 96B 3 22 2020 sass
-
修改index.html 的静态资源请求路径
href=" --> href="/static/search/ src=" --> src="/static/search/
配置 Nginx 和 网关
配置 Nginx 和 网关
-
修改我们电脑本地的 hosts文件,这里我们把gulimall.com和search.gulimall.com(检索服务)的域名都配置到我们的linux服务器的ip上。
# Gulimall Host Start 192.168.10.10 gulimall.com 192.168.10.10 search.gulimall.com # Gulimall Host End
-
配置Nginx(将search下的请求转给我们的网关服务)
xjx@xjx conf.d % pwd /Users/hgw/Documents/Software/mydata/nginx/conf/conf.d xjx@xjx conf.d % vim gulimall.conf
server { listen 80; server_name gulimall.com *.gulimall.com;
在location的位置我们配置过gulimall的上游服务器是我们的网关服务,所以只要是我们server_name后面配置的域名并且是80端口的话都会转给我们配置的网关服务,并且会根据页面请求的/static开头的请求拿到nginx服务器中/static目录下的静态资源。
重启nginx容器:
docker restart nginx
-
配置网关
修改gulimall-gateway服务/src/main/resources
路径下的 application.yml- id: gulimall_host_route uri: lb://gulimall-product predicates: - Host=gulimall.com - id: gulimall_search_route uri: lb://gulimall-search predicates: - Host=search.gulimall.com
访问 :http://search.gulimall.com/ ,成功跳转。
动静分离总结
这里我想了一下,为什么能这样实现动静分离(index.html页面在本机,它要的静态资源在linux服务器)。
因为我们发请求会先通过配置的域名解析到我们的linux服务器,然后默认请求端口就是80,nginx服务监控的就是80端口,所以对search.gulimall.com域名(默认请求80端口)就会被nginx拦截到。然后这个请求转发给我们配置的http://gulimall其配置的上游服务器即我们的本机的网关服务。然后网关服务路由匹配(我们在nginx配置文件中配置了nginx请求转发时会转发host)到我们的检索服务,那么默认拿到的就是template包下的index.html。然后这个index页面再发请求要静态资源,然后这些静态资源都被配置了/static/search前缀,并且index这个要静态资源的请求同样是search.gulimall.com域名开头的,所以也会交给nginx拦截处理。然后会匹配到/static的location配置,最后去对应的静态资源目录下拿到静态资源进行返回。
调整页面跳转
首先根据我们商品服务前端页面发的请求修改gulimall-search服务中的index.html文件名修改为 list.html
,并
-
修改的list.html中首页的请求路径为:
http://gulimall.com
-
修改nginx 静态资源路径下的
mydata/nginx/html/static/index/js
下的 catalogLoader.js 中的gulimall请求为你自己的请求:var cata3link = $("<a href=\"http://search.gulimall.com/list.html?catalog3Id="+ctg3.id+"\" style=\"color: #999;\">" + ctg3.name + "</a>");
-
搜索
search()
,进行修改- 第一处:
<a href="javascript:search();"><img src="/static/index/img/img_09.png" /></a>
- 第二处:
function search() { var keyword=$("#searchText").val() window.location.href="http://search.gulimall.com/list.html?keyword="+keyword; }
-
创建一个SearchController,这个Controller需要处理我们商品服务首页发来的http://search.gulimall.com/list.html请求,要不找不到资源了,因为检索服务中没有默认的index首页了。
@GetMapping("/list.html") public String listPage(SearchParam param, Model model, HttpServletRequest request) { param.set_queryString(request.getQueryString()); SearchResult result = mallSearchService.search(param); model.addAttribute("result",result); return "list"; }
处理检索请求
检索条件分析
商品检索三个入口:
- 1)、选择分类进入商品检索
- 2)、输入检索关键字展示检索页
- 3)、选择筛选条件进入
这里我们对检索页面进行分析,得到以下检索请求可能的检索条件(即请求后面带的参数)
- 全文检索:skuTitle
- 排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
- 过滤:hasStock、skuPrice区间、brandld、catalog3Id、attrs
- 聚合:attrs
比如一个请求的完整查询参数为:?catalog3Id=225&keyword=小米&sort=saleCount_desc/asc&hasStock=0/1&brandId=1&brandId=2&attrs=1_其他:安卓&attrs=2_5寸
这里因为请求附带的查询参数太多了,所以我们将其进行封装到一个VO中,这个VO中包含页面所有可能传递过来的查询条件,那么在检索服务中创建一个vo包即 com.atguigu.gulimall.search.vo
。
这里封装VO接收检索参数利用了MVC的特性,让其对请求附带的参数进行自动封装(参数名和属性名相同就会把参数值赋给这个对象的对应属性)
@Data
public class SearchParam {
private String keyword; // 页面传递过来的检索参数,相当于全文匹配关键字
private Long catalog3Id; // 三级分类的id
/**
* sort=saleCount_desc/asc
* sort=skuPrice_asc/desc
* sort=hotScore_asc/desc
*/
private String sort; // 排序条件
/**
* 好多的过滤条件
* hasStock、skuPrice区间、brandId、catalog3Id、
* hasStock=0/1
* skuPrice=1_500/_500/500_
* brandId=1
*
*/
private Integer hasStock=1; // 是否只显示有货 v 0(无库存) 1(有库存)
private String skuPrice; // 价格区间查询
private List<Long> brandId; // 按照品牌进行查询,可以多选
private List<String> attrs; // 按照属性进行筛选
private Integer pageNum=1; // 页码 默认第1页
}
检索返回结果分析
因为我们检索请求最后检索数据会去ES中进行检索,检索的是ES中的数据,并且还要对ES中最后检索出来的数据进行加工,因为还有一些其他信息需要加进返回结果中(比如分页信息),所以检索返回的结果我们一样进行封装,也封装在我们检索服务的vo包下即 com.atguigu.gulimall.search.vo
包。
分析之后的返回结果VO如下:
public class SearchResult {
// 查询到的所有商品信息
private List<SkuEsModel> products;
/**
* 分页信息
*/
private Integer pageNum; // 当前页码
private Long total; // 总记录数
private Integer totalPages;// 总页码
private List<Integer> pageNavs; // 导航页码
private List<BrandVo> brands; // 当前查询到的结果,所有涉及到的品牌
private List<CatalogVo> ctatLogs; // 当前查询到的结果,所有涉及到的分类
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 List<String> attrValue;
}
}
DSL分析
因为是去ES中查数据,所以我们需要使用DSL语句来查数据,下面是对查询条件大概的分析以及一个DSL查询语句(query)的示例。
- 模糊匹配:对应的是must
- 过滤(按照分类、品牌、属性、价格区间、库存):对应filter,其中如果是对一个字段值进行匹配使用term,如果对这个字段的多个值匹配那么是terms。如果我们要匹配的是一个对象的某个属性,那么需要使用nested,并且用path表明是哪个对象。
- 排序:对应sort
- 分页:对应from和size,from是起始数据位置,size是数据总数
- 高亮:对应highlight
- 聚合分析:对应aggs
GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"9",
"7",
"10"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "8"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海斯 (Hisilicon)",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 1,
"highlight": {
"fields": {"skuTitle": {}},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
},
"aggs": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brand_img_agg":{
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"catelog_agg": {
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attrs_agg": {
"nested": {
"path": "attrs"
},
"aggs": {
"attr_id_agg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},
"aggs": {
"attr_name_agg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attr_value_agg": {
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
这里由于需要使用到 brandName
进行检索,需要修改映射,在此进行数据迁移
删掉映射里的所有:或者也可以把doc_values改为true意思就是允许进行数据统计(即聚合分析)
"index" : false,
"doc_values" : false
-
创建一个新的映射
PUT gulimall_product { "mappings": { "properties": { "attrs": { "type": "nested", "properties": { "attrId": { "type": "long" }, "attrName": { "type": "keyword" }, "attrValue": { "type": "keyword" } } }, "brandId": { "type": "long" }, "brandImg": { "type": "keyword" }, "brandName": { "type": "keyword" }, "catalogId": { "type": "long" }, "catalogName": { "type": "keyword" }, "hasStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "saleCount": { "type": "long" }, "skuId": { "type": "long" }, "skuImg": { "type": "keyword" }, "skuPrice": { "type": "keyword" }, "skuTitle": { "type": "text", "analyzer": "ik_smart" }, "spuId": { "type": "keyword" } } } }
-
数据转移
# 迁移数据 POST _reindex { "source": { "index": "product" }, "dest": { "index": "gulimall_product" } }
-
修改 gulimall-search 服务中 sku数据在es中的索引 的常量,因为我们新建了一个映射
package com.atguigu.gulimall.search.constant; public class EsConstant { public static final String PRODUCT_INDEX = "gulimall_product"; // sku数据在es中的索引 }
然后就可以使用aggs进行聚合分析了,这里一定要注意如果我们是对嵌入式对象进行匹配和聚合分析等操作的话,一定要用nested表明·此处是对嵌入式对象进行操作,并用path标明这个要处理的嵌入式对象是谁。
我们可以对聚合分析之后的结果再次进行聚合分析,具体的json代码可以看上面或者官方文档。
SearchRequest构建
检索方法代码分析:
- 准备检索请求即封装检索条件
- 执行检索请求
- 分析相应数据并封装成需要的指定格式
@Service
public class MallSearchServiceImpl implements MallSearchService {
@Autowired
private RestHighLevelClient client;
@Override//下面根据param动态的构建出查询的DSL语句
public SearchResult search(SearchParam param) {
SearchResult result = null;
// 1、准备检索请求
SearchRequest searchRequest = buildSearchRequrest(param);
try {
// 2、执行检索请求
SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
// 3、分析相应数据并封装成需要的指定格式
result = buildSearchResult(response,param);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
下面就是分析检索请求之后需要的检索条件
- 查询:模糊匹配,过滤(按照分类、品牌、属性、库存、价格区间)
- match-模糊匹配
- filter-过滤(按照分类、品牌、属性、库存、价格区间)
- 按照三级分类id查询
- 按照品牌id查询
- 按照所有指定的属性进行查询
- 按照是否拥有库存进行查询
- 按照价格区间进行查询
- 排序
- 分页
- 高亮
- 聚合分析
- 品牌聚合
- 分类聚合
- attr聚合
封装检索条件
/**
* 因为检索条件太多,所以封装一个方法用于准备检索请求然后直接返回一个封装好的检索条件对象
* 模糊匹配:过滤(按照属性、分类、品牌、价格区间、库存)、排序、分页、高亮,聚合分析
* @return SearchRequest
*/
private SearchRequest buildSearchRequrest(SearchParam param) {
// 构建DSL语句的
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* 查询:模糊匹配,过滤(按照分类、品牌、属性、库存、价格区间)
*/
// 1、构建bool-query
BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
// 1.1、match-模糊匹配
if (!StringUtils.isEmpty(param.getKeyword())) {
boolBuilder.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
}
// 1.2、filter-过滤(按照分类、品牌、属性、库存、价格区间)
// 1.2.1、按照三级分类id查询
if (param.getCatalog3Id() != null) {
boolBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
}
// 1.2.2、按照品牌id查询
if (param.getBrandId() != null && param.getBrandId().size()>0) {
boolBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
}
// 1.2.3、按照所有指定的属性进行查询
if (param.getAttrs()!=null && param.getAttrs().size()>0) {
for (String attrStr : param.getAttrs()) {
// attrs=1_其他:安卓&attrs=2_5寸:1.5寸
BoolQueryBuilder nestedBoolBuilder = QueryBuilders.boolQuery();
String[] s = attrStr.split("_");
String attrId = s[0]; // 检索的属性id
String[] attrValues = s[1].split(":"); // 这个属性检索用的值
nestedBoolBuilder.must(QueryBuilders.termQuery("attrs.attrId",attrId));
nestedBoolBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
// 每一个必须都得生成一个嵌入式的查询nested
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolBuilder, ScoreMode.None);
boolBuilder.filter(nestedQuery);
}
}
// 1.2.4、按照是否拥有库存进行查询
if (param.getHasStock() != null) {
boolBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock()==1));
}
// 1.2.5、按照价格区间进行查询
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]);
}
}
boolBuilder.filter(rangeQuery);
}
// 把以前的所有条件都拿来进行封装
sourceBuilder.query(boolBuilder);
/**
* 排序、分页、高亮
*/
// 2、排序
if (!StringUtils.isEmpty(param.getSort())){
String sort = param.getSort();
// sort=saleCount_desc/asc
String[] s = sort.split("_");
SortOrder order = s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
sourceBuilder.sort(s[0], order);
}
// 3、分页
sourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
// 4、高亮
if (!StringUtils.isEmpty(param.getKeyword())) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
sourceBuilder.highlighter(highlightBuilder);
}
/**
* 聚合分析
*/
// 5.1、品牌聚合
TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
brand_agg.field("brandId").size(50);
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1)); // 品牌聚合的子聚合
brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
// TODO 1、聚合brand
sourceBuilder.aggregation(brand_agg);
// 5.2、分类聚合
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
// TODO 2、聚合catalog
sourceBuilder.aggregation(catalog_agg);
// 5.3、属性聚合
NestedAggregationBuilder attrs_agg = AggregationBuilders.nested("attrs_agg", "attrs"); // 嵌入聚合
// 聚合出当前所有的attrId
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
// 聚合分析出当前attr_id对应的名字
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
// 聚合分析出当前attr_id对应所有可能的属性值attrValue
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
attrs_agg.subAggregation(attr_id_agg);
// TODO 3、聚合attr
sourceBuilder.aggregation(attrs_agg);
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
return searchRequest;
}
构建结果数据
- 返回的所有查询到的商品
- 当前所有商品涉及到的所有属性信息
- 保存当前商品所涉及的所有品牌信息
- 保存当前商品所涉及的所有分类信息
- 分页信息
/**
* 构建结果数据
* @param response 执行检索请求获得的响应数据
* @return 封装成需要的指定格式并返回
*/
private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
SearchResult result = new SearchResult();
// 1、返回的所有查询到的商品
SearchHits hits = response.getHits();
List<SkuEsModel> esModels = new ArrayList<>();
if (hits.getHits()!=null && hits.getHits().length>0) {
for (SearchHit hit : hits.getHits()) {
String sourceAsString = hit.getSourceAsString();
SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
if (!StringUtils.isEmpty(param.getKeyword())) {
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
String string = skuTitle.getFragments()[0].string();
esModel.setSkuTitle(string);
}
esModels.add(esModel);
}
}
result.setProducts(esModels);
// 2、当前所有商品涉及到的所有属性信息
List<SearchResult.AttrVo> attrVos = new ArrayList<>();
ParsedNested attrs_agg = response.getAggregations().get("attrs_agg");
ParsedLongTerms attr_id_agg = attrs_agg.getAggregations().get("attr_id_agg");
for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
// 1、得到属性的id
long attrId = bucket.getKeyAsNumber().longValue();
// 2、得到属性的名字
String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
// 3、得到属性的所有值
List<String> attrValues = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
String keyAsString = ((Terms.Bucket) item).getKeyAsString();
return keyAsString;
}).collect(Collectors.toList());
attrVo.setAttrId(attrId);
attrVo.setAttrName(attrName);
attrVo.setAttrValue(attrValues);
attrVos.add(attrVo);
}
result.setAttrs(attrVos);
// 3、保存当前商品所涉及的所有品牌信息
List<SearchResult.BrandVo> brandVos = new ArrayList<>();
ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
for (Terms.Bucket bucket : brand_agg.getBuckets()) {
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
// 1、得到品牌的id
long brandId = bucket.getKeyAsNumber().longValue();
// 2、得到品牌的name
String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
// 3、得到品牌的img
String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
brandVo.setBrandId(brandId);
brandVo.setBrandImg(brandName);
brandVo.setBrandImg(brandImg);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
// 4、保存当前商品所涉及的所有分类信息
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
for (Terms.Bucket bucket : buckets) {
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
// 得到分类id
catalogVo.setCatalogId(bucket.getKeyAsNumber().longValue());
// 得到分类name
ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
String catalog_name = catalog_name_agg.getBuckets().get(0).getKeyAsString();
catalogVo.setCatalogName(catalog_name);
catalogVos.add(catalogVo);
}
result.setCtatLogs(catalogVos);
// 5、分页信息
// 分页信息-页码
result.setPageNum(param.getPageNum());
// 分页信息-总记录数
long total = hits.getTotalHits().value;
result.setTotal(total);
// 分页信息-总页码 (总记录数 对 每页数求余数)
int totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE==0 ? ((int)total/EsConstant.PRODUCT_PAGESIZE) : ((int)total/EsConstant.PRODUCT_PAGESIZE+1);
result.setTotalPages(totalPages);
// 分页信息-导航栏
List<Integer> pageNavs = new ArrayList<>();
for (int i = 1; i <= totalPages; i++) {
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
return result;
}
这里封装检索条件和构建结果数据业务本质并不复杂,就是先从ES中取出数据,然后再把取出的数据封装成前端需要的格式返回回去。但是正是因为用到了ES,调用了其封装的很多API所以看起来有些麻烦。这里代码可以跟着过一遍,不用记住,用到了再去看官方文档怎么操作的API就可以了。
页面检索效果
页面基本渲染
- 修改排序内容
- 删掉多余的
<div class="rig_tab>
只留下一个div - 删掉
<div class="rig_tab>
盒子下的三个div, 只留下一个 - 修改价格
- 修改图片的路径
- 修改skuTitle
- 删掉多余的
- 修改品牌
- 删掉多余 li
- 进行修改
- 修改规格属性
- 增加分类信息
<!--商品筛选和排序-->
<div class="JD_banner w">
<div class="JD_nav">
<div class="JD_selector">
<!--手机商品筛选-->
<div class="title">
<h3><b>手机</b><em>商品筛选</em></h3>
<div class="st-ext">共 <span>10135</span>个商品 </div>
</div>
<div class="JD_nav_logo">
<!--品牌-->
<div class="JD_nav_wrap">
<div class="sl_key">
<span><b>品牌:</b></span>
</div>
<div class="sl_value">
<div class="sl_value_logo">
<ul>
<li th:each="brand:${result.brands}">
<a href="/static/search/#">
<img th:src="${brand.brandImg}" alt="">
<div th:text="${brand.brandName}">
华为(HUAWEI)
</div>
</a>
</li>
</ul>
</div>
</div>
<div class="sl_ext">
<a href="/static/search/#">
更多
<i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="/static/search/#">
多选
<i>+</i>
<span>+</span>
</a>
</div>
</div>
<!--分类-->
<div class="JD_pre">
<div class="sl_key">
<span><b>分类:</b></span>
</div>
<div class="sl_value">
<ul>
<li th:each="catalog:${result.ctatLogs}"><a href="/static/search/#" th:text="${catalog.catalogName}">分类</a></li>
</ul>
</div>
<div class="sl_ext">
<a href="/static/search/#">
更多
<i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="/static/search/#">
多选
<i>+</i>
<span>+</span>
</a>
</div>
</div>
<!--其他所有需要展示的属性-->
<div class="JD_pre" th:each="attr:${result.attrs}">
<div class="sl_key">
<span th:text="${attr.attrName}">屏幕尺寸:</span>
</div>
<div class="sl_value">
<ul>
<li th:each="val:${attr.attrValue}"><a href="/static/search/#" th:text="${val}">5.56英寸及以上</a></li>
</ul>
</div>
</div>
</div>
<div class="JD_show">
<a href="/static/search/#">
<span>
更多选项( CPU核数、网络、机身颜色 等)
</span>
</a>
</div>
</div>
<!--排序-->
<div class="JD_banner_main">
<!--商品精选-->
<div class="JD_con_left">
<div class="JD_con_left_bar">
<div class="JD_con_one">
<div class="mt">
<h3>商品精选</h3>
<span>广告</span>
</div>
<div class="mc">
<ul>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="/static/search/#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>华为 HUAWEI nova 2S 全面屏四摄 6GB +64GB 曜石黑 移动联通电信4G手机 双卡双待</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥2999.00</span>
</strong>
<span class="mc-ico" title="购买本商品送赠品">
<i class="goods-icons">赠品</i>
</span>
</div>
<div class="mc_rev">
已有
<a href="/static/search/#" class="number">12466</a>
人评价
</div>
</li>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="/static/search/#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>华为 HUAWEI nova 2S 全面屏四摄 6GB +64GB 曜石黑 移动联通电信4G手机 双卡双待</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥2999.00</span>
</strong>
<span class="mc-ico" title="购买本商品送赠品">
<i class="goods-icons">赠品</i>
</span>
</div>
<div class="mc_rev">
已有
<a href="/static/search/#" class="number">12466</a>
人评价
</div>
</li>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/593ba628n8794c6a6.jpg" alt=""></a>
<a href="/static/search/#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥1799.00</span>
</strong>
<span class="mc-ico" title="购买本商品送赠品">
<i class="goods-icons">赠品</i>
</span>
</div>
<div class="mc_rev">
已有
<a href="/static/search/#" class="number">15600</a>
人评价
</div>
</li>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/5919637an271a1301.jpg" alt=""></a>
<a href="/static/search/#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>vivo Xplay6 全网通 6GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥3498.00</span>
</strong>
<span class="mc-ico" title="购买本商品送赠品">
<i class="goods-icons">赠品</i>
</span>
</div>
<div class="mc_rev">
已有
<a href="/static/search/#" class="number">5369</a>
人评价
</div>
</li>
</ul>
</div>
</div>
<div class="JD_con_one">
<div class="mt">
<h3>达人选购</h3>
</div>
<div class="mc">
<ul>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="/static/search/#">
<em>华为 HUAWEI nova 2S 全面屏四摄 6GB +64GB 曜石黑 移动联通电信4G手机 双卡双待</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥2999.00</span>
</strong>
</div>
</li>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="/static/search/#">
<em>华为 HUAWEI nova 2S 全面屏四摄 6GB +64GB 曜石黑 移动联通电信4G手机 双卡双待</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥2999.00</span>
</strong>
</div>
</li>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/593ba628n8794c6a6.jpg" alt=""></a>
<a href="/static/search/#">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥1799.00</span>
</strong>
</div>
</li>
<li>
<a href="/static/search/#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/5919637an271a1301.jpg" alt=""></a>
<a href="/static/search/#">
<em>vivo Xplay6 全网通 6GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待</em>
</a>
<div class="mc_price">
<strong class="price">
<span class="J-p-5963064">¥3498.00</span>
</strong>
</div>
</li>
</ul>
</div>
</div>
<div class="JD_con_one" style="border:none;">
<div class="mt">
<h3>商品精选</h3>
<span>广告</span>
</div>
<div class="mc">
<ul>
<li>
<a href="/static/search/#"><img src="/static/search/img/599a806bn9d829c1c.jpg" alt=""></a>
</li>
<li>
<a href="/static/search/#"><img src="/static/search/img/593e4de0n5ff878a4.jpg" alt=""></a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!--综合排序-->
<div class="JD_con_right">
<div class="filter">
<!--综合排序-->
<div class="filter_top">
<div class="filter_top_left">
<a href="/static/search/#">综合排序</a>
<a href="/static/search/#">销量</a>
<a href="/static/search/#">价格</a>
<a href="/static/search/#">评论分</a>
<a href="/static/search/#">上架时间</a>
</div>
<div class="filter_top_right">
<span class="fp-text">
<b>1</b><em>/</em><i>169</i>
</span>
<a href="/static/search/#" class="prev"><</a>
<a href="/static/search/#" class="next"> > </a>
</div>
</div>
<!--收货地址-->
<div class="filter_bottom">
<div class="filter_bottom_left">
<div class="fs-cell">收货地</div>
<div class="dizhi">
<div class="dizhi_show">
<em>北京朝阳区三环以内</em>
<b></b>
</div>
</div>
<div class="dizhi_con">
<ul id="tab">
<li id="tab1" value="1">北京 <img src="/static/search/image/down-@1x.png" alt=""></li>
<li id="tab2" value="2">朝阳 <img src="/static/search/image/down-@1x.png" alt=""></li>
<li id="tab3" value="3">三环以内 <img src="/static/search/image/down-@1x.png" alt=""></li>
</ul>
<div id="container">
<div id="content1" style="z-index: 1;">
<a href="/static/search/#">北京</a>
<a href="/static/search/#">上海</a>
<a href="/static/search/#">天津</a>
<a href="/static/search/#">重庆</a>
<a href="/static/search/#">河北</a>
<a href="/static/search/#">山西</a>
<a href="/static/search/#">河南</a>
<a href="/static/search/#">辽宁</a>
<a href="/static/search/#">吉林</a>
<a href="/static/search/#">黑龙江</a>
<a href="/static/search/#">内蒙古</a>
<a href="/static/search/#">江苏</a>
<a href="/static/search/#">山东</a>
<a href="/static/search/#">安徽</a>
<a href="/static/search/#">浙江</a>
<a href="/static/search/#">福建</a>
<a href="/static/search/#">湖北</a>
<a href="/static/search/#">湖南</a>
<a href="/static/search/#">广东</a>
<a href="/static/search/#">广西</a>
<a href="/static/search/#">江西</a>
<a href="/static/search/#">四川</a>
<a href="/static/search/#">海南</a>
<a href="/static/search/#">贵州</a>
<a href="/static/search/#">云南</a>
<a href="/static/search/#">西藏</a>
<a href="/static/search/#">陕西</a>
<a href="/static/search/#">甘肃</a>
<a href="/static/search/#">青海</a>
<a href="/static/search/#">宁夏</a>
<a href="/static/search/#">新疆</a>
<a href="/static/search/#">港澳</a>
<a href="/static/search/#">台湾</a>
<a href="/static/search/#">钓鱼岛</a>
<a href="/static/search/#">海外</a>
</div>
<div id="content2">
<a href="/static/search/#">朝阳区</a>
<a href="/static/search/#">海淀区</a>
<a href="/static/search/#">西城区</a>
<a href="/static/search/#">东城区</a>
<a href="/static/search/#">大兴区</a>
<a href="/static/search/#">丰台区</a>
<a href="/static/search/#">昌平区</a>
<a href="/static/search/#">顺义区</a>
</div>
<div id="content3">
<a href="/static/search/#">三环以内</a>
<a href="/static/search/#">管庄</a>
<a href="/static/search/#">北苑</a>
<a href="/static/search/#">定福庄</a>
<a href="/static/search/#">三环到四环之间</a>
<a href="/static/search/#">四环到五环之间</a>
<a href="/static/search/#">五环到六环之间</a>
</div>
</div>
</div>
</div>
<div class="filter_bottom_right">
<ul>
<li>
<a href="/static/search/#">
<i></i>
谷粒商城配送
</a>
</li>
<li>
<a href="/static/search/#">
<i></i>
京尊达 </a>
</li>
<li>
<a href="/static/search/#">
<i></i>
货到付款
</a>
</li>
<li>
<a href="/static/search/#">
<i></i>
仅显示有货
</a>
</li>
<li>
<a href="/static/search/#">
<i></i>
可配送全球
</a>
</li>
</ul>
</div>
</div>
<!--排序内容;商品每四个是一组-->
<div class="rig_tab">
<div th:each="product:${result.getProducts()}">
<div class="ico">
<i class="iconfont icon-weiguanzhu"></i>
<a href="/static/search/#">关注</a>
</div>
<p class="da">
<a href="/static/search/#" >
<img th:src="${product.skuImg}" class="dim">
</a>
</p>
<ul class="tab_im">
<li><a href="/static/search/#" title="黑色">
<img th:src="${product.skuImg}"></a></li>
</ul>
<p class="tab_R">
<span th:text="'¥'+${product.skuPrice}">¥5199.00</span>
</p>
<p class="tab_JE">
<a href="/static/search/#" th:utext="${product.skuTitle}">
Apple iPhone 7 Plus (A1661) 32G 黑色 移动联通电信4G手机
</a>
</p>
<p class="tab_PI">已有<span>11万+</span>热门评价
<a href="/static/search/#">二手有售</a>
</p>
<p class="tab_CP"><a href="/static/search/#" title="谷粒商城Apple产品专营店">谷粒商城Apple产品...</a>
<a href='#' title="联系供应商进行咨询">
<img src="/static/search/img/xcxc.png">
</a>
</p>
<div class="tab_FO">
<div class="FO_one">
<p>自营
<span>谷粒商城自营,品质保证</span>
</p>
<p>满赠
<span>该商品参加满赠活动</span>
</p>
</div>
</div>
</div>
</div>
<!--分页-->
<div class="filter_page">
<div class="page_wrap">
<span class="page_span1">
<a href="/static/search/#">
< 上一页
</a>
<a href="/static/search/#" style="border: 0;color:#ee2222;background: #fff">1</a>
<a href="/static/search/#">2</a>
<a href="/static/search/#">3</a>
<a href="/static/search/#" style="border: 0;font-size: 20px;color: #999;background: #fff">...</a>
<a href="/static/search/#">169</a>
<a href="/static/search/#">
下一页 >
</a>
</span>
<span class="page_span2">
<em>共<b>169</b>页 到第</em>
<input type="number" value="1">
<em>页</em>
<a href="/static/search/#">确定</a>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
效果:
页面的筛选条件渲染
将结果的品牌、分类、商品属性进行遍历显示,并且点击某个属性值时可以通过拼接url进行跳转
- 在list页面中编写一个页面跳转方法
- 分类
- 品牌
- 规格属性
<a href="/static/search/#" th:href="${'javascript:searchProducts("brandId",'+brand.brandId+')'}">
<img th:src="${brand.brandImg}" alt="">
<div th:text="${brand.brandName}">
华为(HUAWEI)
</div>
</a>
123456
<li th:each="catalog:${result.ctatLogs}">
<a href="/static/search/#"
th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}"
th:text="${catalog.catalogName}">分类</a>
</li>
12345
<li th:each="val:${attr.attrValue}">
<a href="/static/search/#"
th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+val+'")'}"
th:text="${val}">5.56英寸及以上</a>
</li>
function searchProducts(name,value){
// 原来的页面
var href = location.href + "";
if (href.indexOf("?")!=-1) {
location.href = location.href + "&"+name+"="+value;
} else {
location.href = location.href + "?"+name+"="+value;
}
}
导航搜索功能
<div class="header_form">
<input id="keyword_input" type="text" placeholder="手机" />
<a href="javascript:searchByKeyword();">搜索</a>
</div>
function searchByKeyword() {
searchProducts("keyword",$("#keyword_input").val());
}
之后做了修改:
function searchProducts(name,value){
location.href = replaceAndAddParamVal(location.href,"keyword",value);
}
页面分页数据渲染
<!--分页-->
<div class="filter_page">
<div class="page_wrap">
<span class="page_span1">
<a class="page_a" th:attr="pn=${result.pageNum - 1}" th:if="${result.pageNum>1}">
< 上一页
</a>
<a class="page_a" th:attr="pn=${nav},style=${nav == result.pageNum?'border: 0;color:#ee2222;background: #fff':''}"
th:each="nav:${result.pageNavs}">[[${nav}]]</a>
<a class="page_a" th:attr="pn=${result.pageNum + 1}" th:if="${result.pageNum<result.totalPages}">
下一页 >
</a>
</span>
<span class="page_span2">
<em>共<b>[[${result.totalPages}]]</b>页 到第</em>
<input type="number" value="1">
<em>页</em>
<a class="page_submit" >确定</a>
</span>
</div>
</div>
$(".page_a").click(function () {
var pn = $(this).attr("pn");
var href = location.href;
if (href.indexOf("pageNum")!=-1){
// 替换pageNum的值
location.href = replaceParamVal(href,"pageNum",pn);
} else if (href.indexOf("?")!=-1){
location.href = location.href + "&pageNum="+pn;
} else {
location.href = location.href + "?pageNum="+pn;
}
return false;
})
function replaceParamVal(url,paramName,replaceVal) {
var oUrl = url.toString();
var re=eval('/('+paramName+'=)([^&]*)/gi');
var nUrl = oUrl.replace(re,paramName+"="+replaceVal);
return nUrl;
}
页面排序功能
- 页面排序功能需要保证,点击某个按钮时,样式会变红,并且其他的样式保持最初的样子;
- 点击某个排序时首先按升序显示,再次点击再变为降序,并且还会显示上升或下降箭头
- 页面排序跳转的思路是通过点击某个按钮时会向其
class
属性添加/去除desc
,并根据属性值进行url拼接
<div class="filter_top">
<div class="filter_top_left" th:with="p = ${param.sort}">
<a th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc'))?'sort_a desc':'sort_a'}"
th:attr="style=${(#strings.isEmpty(p) || #strings.startsWith(p,'hotScore'))
?'color: #FFF;border-color:#e4393c;background:#e4393c'
:'color: #333;border-color:#CCC;background:#FFF'}"
sort="hotScore" href="/static/search/#">
综合排序[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc'))?'↓':'↑'}]]
</a>
<a th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') && #strings.endsWith(p,'desc'))?'sort_a desc':'sort_a'}"
th:attr="style=${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount'))
?'color: #FFF;border-color:#e4393c;background:#e4393c'
:'color: #333;border-color:#CCC;background:#FFF'}"
sort="saleCount" href="/static/search/#">
销量[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') && #strings.endsWith(p,'desc'))?'↓':'↑'}]]
</a>
<a th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') && #strings.endsWith(p,'desc'))?'sort_a desc':'sort_a'}"
th:attr="style=${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice'))
?'color: #FFF;border-color:#e4393c;background:#e4393c'
:'color: #333;border-color:#CCC;background:#FFF'}"
sort="skuPrice" href="/static/search/#">
价格[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') && #strings.endsWith(p,'desc'))?'↓':'↑'}]]
</a>
<a href="/static/search/#">评论分</a>
<a href="/static/search/#">上架时间</a>
</div>
<div class="filter_top_right">
<span class="fp-text">
<b>1</b><em>/</em><i>169</i>
</span>
<a href="/static/search/#" class="prev"><</a>
<a href="/static/search/#" class="next"> > </a>
</div>
</div>
function replaceAndAddParamVal(url,paramName,replaceVal) {
var oUrl = url.toString();
// 1、如果没有就添加,有就替换
if (oUrl.indexOf(paramName)!=-1){
var re=eval('/('+paramName+'=)([^&]*)/gi');
var nUrl = oUrl.replace(re,paramName+"="+replaceVal);
return nUrl;
}else{
var nUrl = "";
if (oUrl.indexOf("?")!=-1) {
nUrl = oUrl + "&" + paramName+"="+replaceVal;
} else {
nUrl = oUrl + "?" + paramName+"="+replaceVal;
}
}
return nUrl;
}
$(".sort_a").click(function () {
// 1、改变当前元素以及兄弟元素的样式
// changeStyle(this);
$(this).toggleClass("desc");
// 2、跳转到指定位置
var sort = $(this).attr("sort");
sort = $(this).hasClass("desc")?sort+"_desc":sort+"_asc";
location.href = replaceAndAddParamVal(location.href,"sort",sort);
// 禁用默认行为
return false;
});
页面价格区间搜索
<a href="/static/search/#">评论分</a>
<a href="/static/search/#">上架时间</a>
<input id="skuPriceFrom" type="number" th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}" style="width: 100px; margin-left: 30px;"/> -
<input id="skuPriceTo" type="number" th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}" style="width: 100px;"/>
<button id="skuPriceSearchBtn">确定</button>
价格区间搜索函数:
$("#skuPriceSearchBtn").click(function () {
// 1、拼上价格区间的查询条件
var from = $("#skuPriceFrom").val();
var to = $("#skuPriceTo").val();
var query = from + "_" + to;
location.href = replaceAndAddParamVal(location.href,"skuPrice",query);
});
是否只显示有库存
<li>
<a href="#" th:with="check = ${param.hasStock}">
<input id="showHasStock" type="checkbox" th:checked="${#strings.equals(check,'1')}" >
仅显示有货
</a>
</li>
$("#showHasStock").change(function () {
if ($(this).prop('checked')){
location.href = replaceAndAddParamVal(location.href,"hasStock",1);
}else{
// 没选中
var re = eval('/(&hasStock=)([^&]*)/gi');
location.href = (location.href+"").replace(re,'');
}
return false;
});
面包屑导航
后端接口编写
第一步、编写远程调用gulimall-product服务的方法
- 定义springcloud的版本
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.4.2</elasticsearch.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
- 添加依赖管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 修改gulimall-product中info方法Serice层实现类方法,加入缓存
@Cacheable(value = "attr",key = "'attrinfo:'+#root.args[0]")
@Override
public AttrRespVo getAttrInfo(Long attrId) {
AttrRespVo respVo = new AttrRespVo();
AttrEntity attrEntity = this.getById(attrId);
//.....
}
- 编写接口
@FeignClient("gulimall-product")
public interface ProductFeignService {
@GetMapping("/product/attr/info/{attrId}")
public R attrInfo(@PathVariable("attrId") Long attrId);
}
第二步、修改检索返回信息类VO
在 SearchResult 实体类中加入面包屑导航数据
// 面包屑导航数据
private List<NavVO> navs;
@Data
public static class NavVO{
private String navName;
private String navValue;
private String link;
}
第三步、修改com.atguigu.common.utils.R类,代码如下
public <T> T getData(String key,TypeReference<T> typeReference){
Object data = get(key); // 默认是map
String s = JSON.toJSONString(data);
T t = JSON.parseObject(s, typeReference);
return t;
}
第四步、创建一个AttrResponseVo类,接收远程查询过来的数据
@Data
public class AttrResponseVo {
/**
* 属性id
*/
private Long attrId;
/**
* 属性名
*/
private String attrName;
/**
* 是否需要检索[0-不需要,1-需要]
*/
private Integer searchType;
/**
* 值类型[0-为单个值,1-可以选择多个值]
*/
private Integer valueType;
/**
* 属性图标
*/
private String icon;
/**
* 可选值列表[用逗号分隔]
*/
private String valueSelect;
/**
* 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]
*/
private Integer attrType;
/**
* 启用状态[0 - 禁用,1 - 启用]
*/
private Long enable;
/**
* 所属分类
*/
private Long catelogId;
/**
* 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整
*/
private Integer showDesc;
private Long attrGroupId;
/**
* "catelogName": "手机/数码/手机", //所属分类名字
* "groupName": "主体", //所属分组名字
*/
private String catelogName;
private String groupName;
private Long[] catelogPath;
}
第五步、修改Controller类
@GetMapping("/list.html")
public String listPage(SearchParam param, Model model, HttpServletRequest request){
param.set_queryString(request.getQueryString());
// 1、根据传递过来的页面的查询参数,去es中检索商品
SearchResult result = mallSearchService.search(param);
model.addAttribute("result",result);
return "list";
}
第六步、Searvice实现类方法修改
MallSearchServiceImpl实现类的buildSearchResult方法下:
if (param.getAttrs()!=null && param.getAttrs().size()>0 ) {
List<SearchResult.NavVO> navVOs = param.getAttrs().stream().map(attr -> {
// 1、分析每一个attrs传过来的查询参数值
SearchResult.NavVO navVO = new SearchResult.NavVO();
String[] s = attr.split("_");
navVO.setNavValue(s[1]);
R r = productFeignService.attrInfo(Long.parseLong(s[0]));
if (r.getCode()==0) {
AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
});
navVO.setNavName(data.getAttrName());
} else {
navVO.setNavName(s[0]);
}
// 2、取消了这个面包屑以后,我们要跳转到哪个地方。将请求地址的url里面的当前条件置空
// 拿到所有的查询条件,去掉当前。
String encode = null;
try {
encode = URLEncoder.encode(attr, "UTF-8");
encode = encode.replace("+", "%20"); // 浏览器对空格的编码和Java不一样
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = param.get_queryString().replace("&attrs=" + encode, "");
navVO.setLink("http://search.gulimall.cn/list.html?"+replace);
return navVO;
}).collect(Collectors.toList());
result.setNavs(navVOs);
}
前端渲染
<!--遍历面包屑功能-->
<div class="JD_ipone_one c">
<a th:href="${nav.link}" th:each="nav:${result.navs}">
<span th:text="${nav.navName}"></span>
<span>:</span>
<span th:text="${nav.navValue}"></span>
x
</a>
</div>
123456789
面包屑导航【条件筛选联动】
gulimall-product服务查询品牌接口
第一步、编写Controller层方法
@GetMapping("/infos")
public R info(@RequestParam("brandIds") List<Long> brandIds){
List<BrandEntity> brand = brandService.getBrandsByIds(brandIds);
return R.ok().put("brand", brand);
}
第二步、Service层实现类编写
@Override
public List<BrandEntity> getBrandsByIds(List<Long> brandIds) {
return baseMapper.selectList(new QueryWrapper<BrandEntity>().in("brandId", brandIds));
}
第三步、gulimall-search服务中编写Feign接口
@FeignClient("gulimall-product")
public interface ProductFeignService {
@GetMapping("/product/brand/infos")
public R brandsInfo(@RequestParam("brand_id") List<Long> brandIds);
}
接口实现类编写
MallSearchServiceImpl 实现类的中的buildSearchResult方法
// 7、品排、分类
if (param.getBrandId()!=null && param.getBrandId().size()>0) {
List<SearchResult.NavVO> navs = result.getNavs();
SearchResult.NavVO navVO = new SearchResult.NavVO();
navVO.setNavName("品牌");
// TODO 远程查询所有品牌
R r = productFeignService.brandsInfo(param.getBrandId());
if (r.getCode()==0) {
List<BrandVo> brand = r.getData("brand", new TypeReference<List<BrandVo>>() {
});
StringBuffer buffer = new StringBuffer();
String replace = "";
for (BrandVo brandVo : brand) {
buffer.append(brandVo.getBrandName()+";");
replace = replaceQueryString(param, brandVo.getBrandId()+"" ,"brandId");
}
navVO.setNavValue(buffer.toString());
navVO.setLink("http://search.gulimall.cn/list.html?"+replace);
}
navs.add(navVO);
}
private String replaceQueryString(SearchParam param, String value,String key) {
String encode = null;
try {
encode = URLEncoder.encode(value, "UTF-8");
encode = encode.replace("+", "%20"); // 浏览器对空格的编码和Java不一样
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = param.get_queryString().replace("&"+key+"=" + encode, "");
return replace;
}
前端页面渲染
<div class="JD_nav_logo" th:with="brandid = ${param.brandId}">
<!--品牌-->
<div th:if="${#strings.isEmpty(brandid)}" class="JD_nav_wrap">
<div class="sl_key">
<span><b>品牌:</b></span>
</div>
<div class="sl_value">
<div class="sl_value_logo">
<ul>
<li th:each="brand:${result.brands}">
<a href="/static/search/#" th:href="${'javascript:searchProducts("brandId",'+brand.brandId+')'}">
<img th:src="${brand.brandImg}" alt="">
<div th:text="${brand.brandName}">
华为(HUAWEI)
</div>
</a>
</li>
</ul>
</div>
</div>
<div class="sl_ext">
<a href="/static/search/#">
更多
<i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="/static/search/#">
多选
<i>+</i>
<span>+</span>
</a>
</div>
</div>
<!--分类-->
<div class="JD_pre">
<div class="sl_key">
<span><b>分类:</b></span>
</div>
<div class="sl_value">
<ul>
<li th:each="catalog:${result.ctatLogs}">
<a href="/static/search/#"
th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}"
th:text="${catalog.catalogName}">分类</a>
</li>
</ul>
</div>
<div class="sl_ext">
<a href="/static/search/#">
更多
<i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="/static/search/#">
多选
<i>+</i>
<span>+</span>
</a>
</div>
</div>
<!--其他所有需要展示的属性-->
<div class="JD_pre" th:each="attr:${result.attrs}" th:if="${!#lists.contains(result.attrIds,attr.attrId)}">
<div class="sl_key">
<span th:text="${attr.attrName}">屏幕尺寸:</span>
</div>
<div class="sl_value">
<ul>
<li th:each="val:${attr.attrValue}">
<a href="/static/search/#"
th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+val+'")'}"
th:text="${val}">5.56英寸及以上</a>
</li>
</ul>
</div>
</div>
</div>
感谢耐心看到这里的同学,觉得文章对您有帮助的话希望同学们不要吝啬您手中的赞,动动您智慧的小手,您的认可就是我创作的动力!
之后还会勤更自己的学习笔记,感兴趣的朋友点点关注哦。