商城业务-检索服务
1.检索业务分析
商品检索三个入口:
1).选择分类进入商品检索
http://search.gulimall.com/list.html?keyword=a
2).输入检索关键字展示检索页
http://search.gulimall.com/list.html?catalog3Id=169
3).选择筛选条件进入
检索条件&排序条件
全文检索:skuTitle
排序:saleCount、hotScore、skuPrice
过滤:hasStock、skuPrice 区间、brandId、catalogId、attrs
聚合:attrs
完整的 url 参数
keyword=小米&sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandId=1
&catalogId=1&attrs=1_3G:4G:5G&attrs=2_骁龙 845&attrs=4_高清屏
4).封装页面传过来参数的实体类vo
请求的参数 SearchParam
@Data
public class SearchParam {
private String keyword;//页面传递过来的全文匹配关键字 v
private Long catalog3Id;//三级分类 id v
//sort=saleCount_asc/desc、sort=skuPrice_asc/desc、sort=hotScore_asc/desc
private String sort;//排序条件 v
/**
*过滤条件
* hasStock(是否有货)、skuPrice 区间、brandId、catalog3Id、attrs
* 评分:hasStock=0/1
* 价格区间:skuPrice=1_500/_500/500_
* 品牌: brandId=1
* 属性: attrs=2_5 存:6 寸
*/
private Integer hasStock;//是否只显示有货 v 0(无库存)1(有库存)
private String skuPrice;//价格区间查询 v
private List<Long> brandId;//按照品牌进行查询,可以多选 v
private List<String> attrs;//按照属性进行筛选 v
private Integer pageNum = 1;//页码
private String _queryString;//原生的所有查询条件
}
响应的参数 SearchResult
//SearchResponse
@Data
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> catalogs;//当前查询到的结果,所有涉及到的所有分类
private List<AttrVo> attrs;//当前查询到的结果,所有涉及到的所有属性
//==========以上是返回给页面的所有信息============
//面包屑导航数据
private List<NavVo> navs = new ArrayList<>();
private List<Long> attrIds = new ArrayList<>();
@Data
public static class NavVo{
private String navName;
private String navValue;
private String link;
}
@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;
}
}
5).SearchController- - - es检索服务
//es检索服务
@Controller
public class SearchController {
@Autowired
MailSearchService mailSearchService;
//根据条件查询数据
//自动将页面提交过来的所有请求的查询参数,封装成指定对象
@GetMapping("/list.html")
public String listPage(SearchParam searchParam,Model model){
//根据查询参数,使用es检索商品
SearchResult result= mailSearchService.search(searchParam);
model.addAttribute("result",result);
return "list";
}
}
6).实现类
es检索服务的 search方法 代码在:
search方法 代码
6.1).实现属性、品牌、分类的点击与跳转
<!--品牌-->
<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="JD_pre">
<div class="sl_key">
<span><b>分类:</b></span>
</div>
<div class="sl_value">
<div class="sl_value_logo">
<ul>
<li th:each="catalog : ${result.catalogs}">
<a href="/static/search/#"
th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}"
th:text="${catalog.catalogName}">华为(HUAWEI)</a>
</li>
</ul>
</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>
js代码
//es点击品牌、属性、分类的方法
function searchProducts(name, value) {
//原來的页面
location.href = replaceParamVal(location.href, name, value)
}
function replaceParamVal(url, paramName, replaceVal, forceAdd) {
var oUrl = url.toString();
var nUrl;
if (oUrl.indexOf(paramName) != -1) {
if (forceAdd) {
if (oUrl.indexOf("?") != -1) {
nUrl = oUrl + "&" + paramName + "=" + replaceVal;
} else {
nUrl = oUrl + "?" + paramName + "=" + replaceVal;
}
} else {
var re = eval('/(' + paramName + '=)([^&]*)/gi');
nUrl = oUrl.replace(re, paramName + '=' + replaceVal);
}
} else {
if (oUrl.indexOf("?") != -1) {
nUrl = oUrl + "&" + paramName + "=" + replaceVal;
} else {
nUrl = oUrl + "?" + paramName + "=" + replaceVal;
}
}
return nUrl;
};
6.2).输入框检索关键词
html
<div class="header_form">
<input id="keyword_input" type="text" placeholder="手机" th:value="${param.keyword}"/>
<a href="javascript:searchByKeyword();">搜索</a>
</div>
js
//搜索框检索
function searchByKeyword() {
searchProducts("keyword", $("#keyword_input").val());
}
6.3).点击分页及跳转
html
<!--分页-->
<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 id="submit_input" type="number" value="1">
<em>页</em>
<a class="page_submit">确定</a></span>
</div>
</div>
js
//分页的点击数字, 跳转
$(".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;
})
6.4).排序 -综合排序、销量、价格
html代码
<!--综合排序-->
<div class="filter_top">
<div class="filter_top_left" th:with="p = ${param.sort}, priceRange = ${param.skuPrice}">
<a sort="hotScore"
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;' }">
综合排序[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') &&
#strings.endsWith(p,'desc')) ?'↑':'↓' }]]</a>
<a sort="saleCount"
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;' }">
销量[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') &&
#strings.endsWith(p,'desc'))?'↑':'↓' }]]</a>
<a sort="skuPrice"
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;' }">
价格[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') && #strings.endsWith(p,'desc'))?'↑':'↓' }]]</a>
<a href="/static/search/#">评论分</a>
<a href="/static/search/#">上架时间</a>
js与样式
//排序功能
//加样式,修改地址栏
$(".sort_a").click(function () {
changeStyle(this);
let sort = $(this).attr("sort");
sort = $(this).hasClass("desc") ? sort + "_desc" : sort + "_asc";
location.href = replaceParamVal(location.href, "sort", sort, false);
return false;
});
function changeStyle(ele) {
// location.href = replaceParamVal(href, "pageNum", pn,flase);
// color: #333; border-color: #ccc; background: #fff
// color: #fff; border-color: #e4393c; background: #e4393c
$(".sort_a").css({"color": "#333", "border-color": "#ccc", "background": "#fff"});
$(".sort_a").each(function () {
let text = $(this).text().replace("↓", "").replace("↑", "");
$(this).text(text);
})
$(ele).css({"color": "#FFF", "border-color": "#e4393c", "background": "#e4393c"});
$(ele).toggleClass("desc");
if ($(ele).hasClass("desc")) {
let text = $(ele).text().replace("↓", "").replace("↑", "");
text = text + "↓";
$(ele).text(text);
} else {
let text = $(ele).text().replace("↓", "").replace("↑", "");
text = text + "↑";
$(ele).text(text);
}
};
6.5).价格区间
<!--价格区间-->
<input id="skuPriceFrom" type="number" style="width: 100px; margin-left: 30px"
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}">
-
<input id="skuPriceTo" type="number" style="width: 100px;"
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}">
<button id="skuPriceSearchBtn">确定</button>
</div>
js
//价格区间
$("#skuPriceSearchBtn").click(function () {
var from = $("#skuPriceFrom").val();
var to = $("#skuPriceTo").val();
var query = from + "_" + to;
location.href = replaceParamVal(location.href, "skuPrice", query,false);
});
6.6).显示有货
html代码
<li>
<a th:with="check = ${param.hasStock}">
<input id="showHasStock" type="checkbox"
th:checked="${#strings.equals(check,'1')?true:false}">
仅显示有货</a>
</li>
js代码
//库存,显示有货
$("#showHasStock").change(function () {
// alert( $(this).prop("checked") );
if($(this).prop("checked") ) {
location.href = replaceParamVal(location.href,"hasStock",1,false);
} else {
//没选中,清空地址
let re = eval('/(hasStock=)([^&]*)/gi');
location.href = (location.href+"").replace(re,"");
}
return false;
});
6.7).构建面包屑导航功能,点击X,清除地址栏某个拼接的条件(代码见6).实现类es检索服务的 search方法的面包屑导航功能)
6.7-1).清除品牌
<div class="JD_nav_logo" th:with="brandId= ${param.brandId}">
<!--品牌-->
<div th:if="${#strings.isEmpty(brandId)}" class="JD_nav_wrap">
6.7-2).清除属性
<div class="JD_pre" th:each="attr:${result.attrs}" th:if="${!#lists.contains(result.attrIds,attr.attrId)}" >