电商项目——全文检索-ElasticSearch——第一章——中篇
电商项目——商城业务-商品上架——第二章——中篇
电商项目——商城业务-首页——第三章——中篇
电商项目——性能压测——第四章——中篇
电商项目——缓存——第五章——中篇
电商项目——商城业务-检索服务——第六章——中篇
电商项目——商城业务-异步——第七章——中篇
电商项目——商品详情——第八章——中篇
电商项目——认证服务——第九章——中篇
电商项目——购物车——第十章——中篇
电商项目——消息队列——第十一章——中篇
电商项目——订单服务——第十二章——中篇
电商项目——分布式事务——第十三章——中篇
我们接下来就搭建整个商城的检索功能,同样的按照微服务自治的功能,我们的检索功能就放在mall-search下
index.html要引入依赖和标头,并且进行替换(变成linux的nginx中的文件路径/html/)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
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
前面搭建好了页面检索环境,现在我们就来梳理一下检索逻辑
在这之前,我们要配置dev-tools的依赖和配置关闭缓存
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
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;
如上我们就完成了调整页面跳转
前面我们无论是选择分类,还是进行检索关键字,都打通了到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;//页码
}
前面我们通过分析页面,将所有可呢提交的查询参数封装成了SearchParam 对象,我们调用方法把前端提交过来的参数,查询出对应的结果然后通过封装返回给页面
@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";
}
}
mall-search
每次查询一个东西以后,下一次我们要查询的东西是动态变化的,它是根据我们已查到的东西聚合分析出来的(我们查到的东西的属性,值有哪些),我们下节就完成检索DSL测试-聚合部分
https://www.elastic.co/guide/index.html
我在码云中的项目里面已经保存了,我测试检索DSL-聚合部分地址如下,都在mall-search中有一个.json文件结尾的
zlj分布式代码
https://www.elastic.co/guide/index.html
动态的构建出查询所需要的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 = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
return searchRequest;
}
}
https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl
/**
* 准备检索请求
* 模糊匹配。过滤(按照属性,分类,品牌,架构区间,库存),排序,分页,高亮,聚合分析
*/
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);
/**
* 排序,分页,高亮,
*/
//2.1 排序
if (!StringUtils.isEmpty(param.getSort())){
String sort = param.getSort();
//sort=hotScore_asc/desc
String[] s = sort.split("_");
SortOrder order=s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
sourceBuilder.sort(s[0],order);
}
//2.2 分页 pagesize=5
//pageNum:1 from:0 size:5
//pageNum:2 from:5 size:5
//from=pageNum-1 *pagesize
sourceBuilder.from(Math.toIntExact((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE));
sourceBuilder.size(Math.toIntExact(EsConstant.PRODUCT_PAGESIZE));
//2.3 高亮,
if (!StringUtils.isEmpty(param.getKeyword())){
HighlightBuilder builder = new HighlightBuilder();
builder.field("skuTitle");
builder.preTags("");
builder.postTags("");
sourceBuilder.highlighter(builder);
}
/**
* 聚合分析
*/
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
return searchRequest;
}
https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl
/**
* 准备检索请求
* 模糊匹配。过滤(按照属性,分类,品牌,架构区间,库存),排序,分页,高亮,聚合分析
*/
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);
/**
* 排序,分页,高亮,
*/
//2.1 排序
if (!StringUtils.isEmpty(param.getSort())){
String sort = param.getSort();
//sort=hotScore_asc/desc
String[] s = sort.split("_");
SortOrder order=s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
sourceBuilder.sort(s[0],order);
}
//2.2 分页 pagesize=5
//pageNum:1 from:0 size:5
//pageNum:2 from:5 size:5
//from=pageNum-1 *pagesize
sourceBuilder.from(Math.toIntExact((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE));
sourceBuilder.size(Math.toIntExact(EsConstant.PRODUCT_PAGESIZE));
//2.3 高亮,
if (!StringUtils.isEmpty(param.getKeyword())){
HighlightBuilder builder = new HighlightBuilder();
builder.field("skuTitle");
builder.preTags("");
builder.postTags("");
sourceBuilder.highlighter(builder);
}
/**
* 聚合分析
*/
//1:todo 品牌聚合
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));
sourceBuilder.aggregation(brand_agg);
//2:todo 分类聚合catalog_agg
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(1);
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
sourceBuilder.aggregation(catalog_agg);
//3:todo 属性聚合
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
//聚合出当前所有的attrId
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
//聚合出当前所有的attrId对应的名字
attr_id_agg.subAggregation( AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
//聚合出当前所有的attrId对应的所有可呢属性值attrValue
attr_id_agg.subAggregation( AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
attr_agg.subAggregation(attr_id_agg);
sourceBuilder.aggregation(attr_agg);
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
return searchRequest;
}
https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl
/**
* 构建结果数据
* @param response
* @return
*/
private SearchResult buildSearchResult(SearchResponse response,SearchParam param) {
SearchResult result = new SearchResult();
//返回给所有查询到的商品
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 skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
esModels.add(skuEsModel);
}
}
//1:返回的所有查询到的商品
result.setProducts(esModels);
//2:当前所有商品涉及到的所有属性信息
List<SearchResult.AttrVo> attrVos=new ArrayList<>();
ParsedNested attr_agg = response.getAggregations().get("attr_agg");
ParsedLongTerms attr_id_agg = attr_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 attr_name_agg = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
//3:得到属性的所有值
List<String> attr_value_agg = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
String keyAsString = ((Terms.Bucket) item).getKeyAsString();
return keyAsString;
}).collect(Collectors.toList());
attrVo.setAttrName(attr_name_agg);
attrVo.setAttrValue(attr_value_agg);
attrVo.setAttrId(attrId);
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:得到品牌的名字
String brand_name_agg = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
//3:得到品牌的图片
String brand_img_agg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
brandVo.setBrandImg(brand_img_agg);
brandVo.setBrandName(brand_name_agg);
brandVo.setBrandId(brandId);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
//4:当前所有商品涉及到的所有分类信息
ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
List<SearchResult.CatalogVo> catalogVos=new ArrayList<>();
for (Terms.Bucket bucket:buckets){
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
//得到分类id
String keyAsString = bucket.getKeyAsString();
catalogVo.setCatalogId(Long.parseLong(keyAsString));
//得到分类名
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.setCatalogs();
//5:分页信息-页码
result.setPageNum(param.getPageNum());
//5:分页信息-总记录数
long total= hits.getTotalHits().value;
result.setTotal(total);
//5:分页信息-总页码
int totalPage= (int) (total%EsConstant.PRODUCT_PAGESIZE==0?total/EsConstant.PRODUCT_PAGESIZE:(total/EsConstant.PRODUCT_PAGESIZE+1));
result.setTotalPage(totalPage);
return result;
}
打断点调试
mall-search
list.html
第一个操作:如下图根据定位找到list.html页面中的(检查元素)class=“rig_tab”
在class="rig_tab"中编写如下内容
<p class="da">
<a href="/static/search/#" >
<img th:src="${product.getSkuImg()}" class="dim">
a>
p>
<ul class="tab_im">
<li><a href="/static/search/#" title="黑色">
<img th:src="${product.getSkuImg()}">a>li>
ul>
<p class="tab_R">
<span th:text="'¥'+${product.getSkuPrice()}">¥5199.00span>
p>
<p class="tab_JE">
<a href="/static/search/#" th:text="${product.getSkuTitle()}">
Apple iPhone 7 Plus (A1661) 32G 黑色 移动联通电信4G手机
a>
p>
进行测试
第二个操作:我们修改如下的界面,(当前所有检索到的商品,所涉及到的属性,它的品牌有多少中,还有分组的属性)
在class="JD_nav_wrap"类中只留下一个ul就行了
<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 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="attrValue : ${attr.attrValue}"><a href="/static/search/#" th:text="${attrValue}">5.56英寸及以上</a></li>
</ul>
</div>
</div>
<div class="JD_pre"">
<div class="sl_key">
<span>分类:span>
div>
<div class="sl_value">
<ul>
<li th:each="catalog : ${result.catalogs}"><a href="/static/search/#" th:text="${catalog.catalogName}">5.56英寸及以上a>li>
ul>
div>
mall-search
list.html
我们现在点击某个属性(如cpu品牌),然后让地址可以动态的拼接上我们点击的值,然后在进行聚合查询
第一个操作:我们要让上面都要跳转的超链接,都要统一的声明一个函数,超链接要动态的取值,示例如下
<li th:each="brand:${result.brands}">
<a th:href="${'javascript:searchProducts("brandId",'+brand.brandName+')'}">
<img th:src="${brand.brandImg}" alt="">
<div th:text="${brand.brandName}">
华为(HUAWEI)
div>
a>
li>
function searchProducts(name,value) {
//跳转到原来的页面
location.href=location.href+"&"+name+"="+value;
}
测试:
第二步操作:按照上面,继续完成品牌三级分类,和品牌属性
<div class="JD_pre"">
<div class="sl_key">
<span>分类:span>
div>
<div class="sl_value">
<ul>
<li th:each="catalog : ${result.catalogs}">
<a th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}" th:text="${catalog.catalogName}">5.56英寸及以上a>
li>
ul>
function searchProducts(name,value) {
//跳转到原来的页面
location.href=location.href+"&"+name+"="+value;
}
测试
修改list.html页面中的searchProducts
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="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="attrValue : ${attr.attrValue}"><a th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+attrValue+'")'}">5.56英寸及以上a>li>
ul>
div>
div>
接下来我们在完成导航搜索功能,如下,(按照关键字进行搜索)
mall-search
list.html
<div class="header_sous">
<div class="logo">
<a href="http://zlj.mall.com"><img src="/static/search/image/logo1.jpg" alt="">a>
div>
<div class="header_form">
<input id="keyword_input" type="text" placeholder="手机" />
<a href="javascript:searchByKeyword();" th:value="${param.keyword}">搜索a>
div>
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;
}
}
function searchByKeyword() {
//把$("#keyword_input").val()传入到keyword中
searchProducts("keyword",$("#keyword_input").val())
}
测试:
第二个操作:我们对分页条进行修改
绑定一个跳转事件(class=“page_a”),上一页,下一页必须要有(必须进行遍历)
在SearchResult加入如下属性
//可遍历的所有导航页
private List pageNavs;
mall-search
MallSearchServiceImpl中的buildSearchResult方法中,我们加入如下值
List<Integer> pageNavs=new ArrayList<>();
for (int i=1;i<totalPage;i++){
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
System.out.println("响应的数据:"+string);
return result;
<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':''}" href="/static/search/#"
th:each="nav:${result.pageNavs }"
>[[${nav}]]a>
<result.totalPage}>
下一页 >
分页必须是,点击第几页就可以跳转的,而不是根据参数来跳转
第三个操作
<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:each="nav:${result.pageNavs + 1}"
th:attr="pn=${nav},style=${nav ==result.pageNum?'border: 0;color:#ee2222;background: #fff':''}"
>[[${nav}]]a>
<a class="page_a" th:attr="pn=${result.pageNum + 1}" th:if="${result.pageNum
下一页 >
"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 {
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;
}
<div class="header_sous">
<div class="logo">
<a href="http://zlj.mall.com"><img src="/static/search/image/logo1.jpg" alt="">a>
div>
<div class="header_form">
<input id="keyword_input" type="text" placeholder="手机"/>
<a href="javascript:searchByKeyword();" th:value="${param.keyword}">搜索a>
div>
mall-search
list.html
实现如下部分功能,为所有综合排序中的a标签加上单击事件,变成点击一个,就会变成高亮模式,其他的就要不高亮
<a class="sort_a" href="/static/search/#">综合排序a>
<a class="sort_a" href="/static/search/#">销量a>
<a class="sort_a" href="/static/search/#">价格a>
<a class="sort_a" href="/static/search/#">评论分a>
<a class="sort_a" href="/static/search/#">上架时间a>
$(".sort_a").click(function () {
//每点击一个a标签,都会为它加上样式
$(".sort_a").csss({"color":"#333","border-color":"#CCC","background":"#FFF"})
$(this).css({"color":"#FFF","border-color":"#e4393c","background":"#e4393c"})
//禁用默认行为
return false;
})
function replaceParamAndAddParamVal(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){
//上一次点击,我们要进行替换pageNum的值,不然,会一直加参数
nUrl=oUrl+"&"+paramName+'='+replaceVal;
}else {
nUrl=oUrl+"?"+paramName+'='+replaceVal;
}
return nUrl;
}
}
$(".sort_a").click(function () {
//:1:当前被点击元素变为选中状态
//改变当前元素以及兄弟元素之间的样式
changeStyle(this)
// //2:跳转到指定位置sort=hotScore_asc/desc * sort=saleCount_asc/desc
// * sort=skuPrice_asc/desc
// * sort=hotScore_asc/desc
var sort=$(this).attr("sort");
sort=$(this).hasClass("desc")?sort+"_desc":sort+"_asc";
location.href=replaceParamAndAddParamVal(location.href,"sort",sort)
//禁用默认行为
return false;
})
function changeStyle(ele) {
$(".sort_a").css({"color":"#333","border-color":"#CCC","background":"#FFF"})
//每点击一个a标签,都会为它加上样式
$(ele).css({"color":"#FFF","border-color":"#e4393c","background":"#e4393c"})
//改变升降序
$(ele).toggleClass("desc");//加上就是降序,不加就是升序
$(".sort_a").each(function () {
var text= $(this).text().replace("s","").replace("w","");
$(this).text(text);
})
if ($(ele).hasClass("desc")){
//降序
var text= $(ele).text().replace("s","").replace("w","");
text=text+"s"
$(ele).text(text);
}else {
var text= $(ele).text().replace("s","").replace("w","");
text=text+"w"
$(ele).text(text);
}
}
我们现在要实现当点击跳转的时候,页面要进行成功回显,而不是像上一章那样只是静态的变化(没有回显)