目录
1 检索服务
1.1 搭建页面环境
1.1.1 引入依赖
1.1.2 将检索页面放到gulimall-search的src/main/resources/templates/目录下
1.1.3 调整搜索页面
1.1.4 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/ search/
1.1.5 SwitchHosts配置域名转发
1.1.6 测试
1.1.7 nginx配置
1.1.8 网关配置
1.1.9 重启测试
1.2 调整页面跳转
1.2.1 引入spring-boot-devtools依赖
1.2.2 关闭thymeleaf缓存
1.2.3 修改页面能够跳转到商城首页
1.2.4 修改index.html文件名
1.3 检索查询参数模型分析抽取
1.3.1 检索条件分析
1.3.2 查询参数封装
1.4 检索返回结果模型分析抽取
1.5 检索DSL测试
1.5.1 DSL查询部分
1.5.1.1 查询部分DSL
1.5.1.2 查询部分+排序+分页+高亮DSL
1.5.2 聚合部分
1.5.2.1 聚合时出现的问题Use doc values instead
1.5.2.1.1 报错原因
1.5.2.1.2 解决方案
1.5.2.2 聚合部分DSL
1.5.3 总的DSL(查询+聚合)
1.6 SearchRequest构建
1.6.1 检索、排序、分页、高亮、聚合
1.6.1.1 controller层
1.6.1.2 service层
1.6.1.3 EsConstant.java(Es常量类)
1.6.2 测试
1.7 SearchResponse分析&封装
1.8 验证结果封装正确性
1.9 渲染检索页面【P184-192】
1.9.1 检索页面完整代码
1.9.2 检索服务后端相关代码
1.9.2.1 引入依赖
1.9.2.2 vo
1.9.2.3 controller
1.9.2.4 service
1.9.2.5 远程调用接口
1.9.3 远程服务相关接口
1.9.3.1 attrInfo接口
1.9.3.2 brandsInfo接口
1 检索服务
1.1 搭建页面环境
1. 引入依赖
2. 将页面复制到gulimall-search的src/main/resources/templates/目录下
3. 调整搜索页面
1)引入thymeleaf
xmlns:th="http://www.thymeleaf.org"
2)修改页面静态资源的引入,加上/static/search
eg:
由
<link rel="stylesheet" href="./css/index.css">
改为:
<link rel="stylesheet" href="/static/search/css/index.css">
4. 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/search/
5. SwitchHosts配置域名转发
1)以管理员身份运行SwitchHosts;
2)让所有的search.gulimall.com可以定位到linux的nginx服务
6. 测试
1)浏览器访问http://localhost:12000
2)浏览器访问http://search.gulimall.com/
7. nginx配置
8. 网关配置
9. 测试 http://search.gulimall.com/
1.1.1 引入依赖
<!-- 模板引擎 :thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
1.1.2 将检索页面放到gulimall-search的src/main/resources/templates/目录下
1.1.3 调整搜索页面
引入thymeleaf,修改引入静态资源的路径以/static/search开始。以下为了举例,list.html完整代码见 1.9.1 检索页面完整代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/search/css/index.css">
<link rel="stylesheet" type="text/css" href="/static/search/font/iconfont.css">
<!--<script src="/static/search/js/jquery-3.2.1.min.js"></script>-->
<script src="/static/search/js/jquery-1.12.4.js"></script>
<title>Document</title>
</head>
...
1.1.4 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/ search/
1.1.5 SwitchHosts配置域名转发
(1)以管理员身份运行SwitchHosts;
(2)让所有的search.gulimall.com可以定位到linux的nginx服务
1.1.6 测试
(1)浏览器访问http://localhost:12000
(2)浏览器访问http://search.gulimall.com/
1.1.7 nginx配置
(1)修改gulimall.conf,将 server_name 由 gulimall.com 改为 *.gulimall.com
(2)保存gulimall.conf并重启nginx。
# 进入/root/docker/nginx/conf/conf.d/
cd /root/docker/nginx/conf/conf.d/
# 进入gulimall.conf,修改server_name
vi gulimall.conf
# 重启nginx
docker restart nginx
# 查看nginx是否启动成功
docker ps -a
1.1.8 网关配置
- 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
1.1.9 重启测试
http://search.gulimall.com/
注意静态资源的路径是否正确;有些图片可能缺失,影响不大。
1.2 调整页面跳转
1.2.1 引入spring-boot-devtools依赖
前面已经引入。
1.2.2 关闭thymeleaf缓存
页面修改后同过Ctrl+Shift+F9对html页面进行重新构建,无需重启项目
spring:
thymeleaf:
cache: false
1.2.3 修改页面能够跳转到商城首页
效果要求:点击搜索页面左上角的谷粒商城首页或谷粒商城都能跳转到商城首页。如下图:
1. 修改页面代码:(跳转路径 http://gulimall.com)
2. 测试:
点击谷粒商城首页或谷粒商城
3. 解决商城首页显示问题
server_name gulimall.com *.gulimall.com;
相关命令:
# 进入gulimall.conf,修改server_name
vi gulimall.conf
# 重启nginx
docker restart nginx
重启nginx,访问 gulimall.com:
1.2.4 修改index.html文件名
1. gulimall-search下的index.html重命名为list.html
原因:在首页点击搜索跳转的链接为list.html
2. 新增由/list.html能跳转到list.thml页面的方法gulimall-search/src/main/java/com/wen/gulimall/search/controller/SearchController.java
@Controller
public class SearchController {
@GetMapping("/list.html")
public String listPage(){
return "list";
}
}
3. 将gulimall-product的index.html页面中的search()方法的调用由img标签放到a标签
4. 重启服务测试
1.3 检索查询参数模型分析抽取
1.3.1 检索条件分析
- 全文检索:skuTitle-》keyword
- 排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
- 过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs
- 聚合:attrs
完整查询参数
keyword=小米&sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandId=1&catalog3Id=1&at trs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏
1.3.2 查询参数封装
gulimall-search/src/main/java/com/wen/gulimall/search/vo/SearchParam.java
/**
* @author W
* @createDate 2023/7/17 16:10
* @description 封装页面所有可能传递过来的查询条件
* catalog3Id=255&keyword=小米&sort=saleCount_asc&hasStock=0/1&bandId=1&bandId=2
*/
@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=0/1
* skuPrice=1_500/_500/500_
* bandId=1
* attrs=2_5寸:6寸
*/
private Integer hasStock; // 是否只显示有货
private String skuPrice;// 价格区间查询
private List<Long> brandId;// 按照品牌进行查询,可以多选
private List<String> attrs;// 按照属性进行筛选
private Integer pageNum;// 页码
}
1.4 检索返回结果模型分析抽取
gulimall-search/src/main/java/com/wen/gulimall/search/vo/SearchResult.java
@Data
public class SearchResult {
// 查询到的所有商品信息
private List<SkuEsModel> products;
/**
* 以下是分页信息
*/
private Integer pageNum; // 当前页码
private Long total; // 总记录数
private Integer totalPages; // 总页码
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 List<String> attrValue;
}
}
1.5 检索DSL测试
1.5.1 DSL查询部分
- 属性数据类型为嵌入式,即"type"="nested",查询、过滤也需要嵌入式。可参照官网:Nested query | Elasticsearch Guide [7.4] | Elastic
- 价格区间使用range gte lte.
1.5.1.1 查询部分DSL
# 查询部分
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": 225
}
},
{
"terms": {
"brandId": [
"1",
"2",
"7"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "12"
}
}
},
{
"terms": {
"attrs.attrValue": [
"HUAWEI Kirin 980",
"A13"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "false"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
]
}
}
}
1.5.1.2 查询部分+排序+分页+高亮DSL
模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
# 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": 225
}
},
{
"terms": {
"brandId": [
"1",
"2",
"7"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "12"
}
}
},
{
"terms": {
"attrs.attrValue": [
"HUAWEI Kirin 980",
"A13"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "false"
}
}
},
{
"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>"
}
}
1.5.2 聚合部分
1.5.2.1 聚合时出现的问题Use doc values instead
1.5.2.1.1 报错原因
1. mapping映射参数index、doc_values使用说明:
- index:index选项控制是否对字段值进行索引。它接受true或false,默认为true。如果为 false, 表示该字段不会被索引, 但是检索结果里面有, 但字段本身不能当做检索条件。
- doc_values:默认为true。如果为false,表示字段不需要进行排序、聚合、或者使用脚本访问字段值,这样可以节省磁盘空间。还可以通过设定doc_values为true,index为false 来让字段不能被检索但是可以用于排序、聚合以及脚本操作。
具体可以参照官网:mapping parameters
2. 错误原因:
brandName属性的doc_values为false不可以进行聚合。
1.5.2.1.2 解决方案
更新映射,删除映射中的index和doc_values参数。
(1)创建新的索引指定映射
PUT gulimall_product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword"
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catalogId": {
"type": "long"
},
"brandName": {
"type": "keyword"
},
"brandImg": {
"type": "keyword"
},
"catalogName": {
"type": "keyword"
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword"
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
(2)迁移数据
# 数据迁移
POST _reindex
{
"source": {
"index": "product"
},
"dest": {
"index": "gulimall_product"
}
}
迁移成功
(3)修改索引常量名
由product改为gulimall_product
public class EsConstant {
public static final String PRODUCT_INDEX = "gulimall_product";
}
1.5.2.2 聚合部分DSL
(1)DSL聚合部分
GET gulimall_product/_search
{
"query": {
"match_all": {}
},
"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
}
}
}
},
"catalog_agg":{
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attr_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
}
}
}
}
}
}
}
}
1.5.3 总的DSL(查询+聚合)
GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": 225
}
},
{
"terms": {
"brandId": [
"1",
"2",
"7"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "12"
}
}
},
{
"terms": {
"attrs.attrValue": [
"HUAWEI Kirin 980",
"A13"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "false"
}
}
},
{
"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
}
}
}
},
"catalog_agg":{
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attr_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
}
}
}
}
}
}
}
}
1.6 SearchRequest构建
1.6.1 检索、排序、分页、高亮、聚合
1.6.1.1 controller层
gulimall-search/src/main/java/com/wen/gulimall/search/controller/SearchController.java
@Controller
public class SearchController {
@Resource
private MallSearchService mallSearchService;
/**
* 自动将页面提交过来的所有请求查询参数封装成指定的对象
* @param searchParam
* @return
*/
@GetMapping("/list.html")
public String listPage(SearchParam searchParam, Model model){
// 1. 根据传递来的页面的查询参数,去es中检索商品
SearchResult result = mallSearchService.search(searchParam);
model.addAttribute("result", result);
return "list";
}
}
1.6.1.2 service层
gulimall-search/src/main/java/com/wen/gulimall/search/service/MallSearchService.java
public interface MallSearchService {
/**
*
* @param searchParam 检索的所有参数
* @return 返回检索的结果,里面包含页面需要的所有信息
*/
SearchResult search(SearchParam searchParam);
}
gulimall-search/src/main/java/com/wen/gulimall/search/service/impl/MallSearchServiceImpl.java
@Service
public class MallSearchServiceImpl implements MallSearchService {
@Resource
private RestHighLevelClient restHighLevelClient;
// 根据条件去es中检索
@Override
public SearchResult search(SearchParam searchParam) {
// 动态的构建出查询所需要的DSL
SearchResult result = null;
// 1. 准备检索请求
SearchRequest searchRequest = buildSearchRequest(searchParam);
try {
// 2. 执行检索请求
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
// 3. 分析响应数据,封装成需要的格式
result = buildSearchResponse(searchParam,searchResponse);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 准备检索请求
* # 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
* @return
*/
private SearchRequest buildSearchRequest(SearchParam searchParam) {
// 构建DSL语句对象
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* 查询:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
*/
// 1. 构建bool - query
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 1.1 bool - must 模糊匹配
if(StrUtil.isNotEmpty(searchParam.getKeyword())){
boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
}
// 1.2 bool - filter - 按照三级分类id查询
if(searchParam.getCatalog3Id() != null){
boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
}
// 1.2 bool -filter - 按照品牌id查询
if(CollectionUtil.isNotEmpty(searchParam.getBrandId())){
boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
}
// 1.2 bool - filter - 按照所有指定的属性进行查询
if(CollectionUtil.isNotEmpty(searchParam.getAttrs())){
// attrs=1_5寸:6寸&attrs=2_8G:16G
for (String attrStr : searchParam.getAttrs()) {
String[] s = attrStr.split("_");
String attrId = s[0]; // 检索的属性id
String[] attrValues = s[1].split(":"); // 这个属性检索所需要的值
BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
// ScoreMode.None 不参与评分
// 每一个必须都得生成一个nested查询
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
boolQuery.filter(nestedQuery);
}
}
// 1.2 bool -filter - 按照库存是否有进行查询
if(searchParam.getHasStock() != null) {
boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
}
// 1.2 bool -filter - 按照价格区间
if(StrUtil.isNotEmpty(searchParam.getSkuPrice())){
String[] s = searchParam.getSkuPrice().split("_");
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
if(s.length == 2){
rangeQuery.gte(s[0]).lte(s[1]);
boolQuery.filter(rangeQuery);
}else if(s.length == 1){
if(searchParam.getSkuPrice().startsWith("_")){
rangeQuery.lte(s[0]);
boolQuery.filter(rangeQuery);
}
if(searchParam.getSkuPrice().endsWith("_")){
rangeQuery.gte(s[0]);
boolQuery.filter(rangeQuery);
}
}
}
// 把以上所有的条件都拿来进行封装
sourceBuilder.query(boolQuery);
/**
* 排序,分页,高亮
*/
// 2.1 排序
if(StrUtil.isNotEmpty(searchParam.getSort())){
// sort=saleCount_asc/desc
String[] s = searchParam.getSort().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 form:5 size:5
// from = (pageNum - 1)*size
sourceBuilder.from((searchParam.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
// 2.3 高亮
if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
HighlightBuilder builder = new HighlightBuilder();
builder.field("skuTitle");
builder.preTags("<b style='color:red'>");
builder.postTags("</b>");
sourceBuilder.highlighter(builder);
}
/**
* 聚合分析
*/
// 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);
// 2. 分类聚合 catalog_agg
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
// 分类聚合的子聚合
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
// TODO 1.聚合catalog
sourceBuilder.aggregation(catalog_agg);
// 3. 属性聚合
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
// 聚合出当前所有的attrId
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(10);
// 聚合出当前所有的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));
attr_agg.subAggregation(attr_id_agg);
// TODO 1.聚合attr
sourceBuilder.aggregation(attr_agg);
System.out.println("构建的DSL语句"+sourceBuilder.toString());
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},sourceBuilder);
return searchRequest;
}
/**
* 封装检索结果
* @param searchParam
* @param searchResponse
* @return
*/
private SearchResult buildSearchResponse(SearchParam searchParam, SearchResponse searchResponse) {
return null;
}
}
buildSearchResponse(searchParam,searchResponse)方法具体实现见 1.7 SearchResponse分析&封装
1.6.1.3 EsConstant.java(Es常量类)
gulimall-search/src/main/java/com/wen/gulimall/search/constant/EsConstant.java
public class EsConstant {
public static final String PRODUCT_INDEX = "gulimall_product";
public static final Integer PRODUCT_PAGESIZE = 2;
}
1.6.2 测试
根据代码中System.out.println("构建的DSL语句"+sourceBuilder.toString());输出的DSL语句,在Kibana中进行测试,看输出结果是否正确。
1.7 SearchResponse分析&封装
对search()接口进行debug,根据debug确定聚合的具体类型。如下图,以分类的聚合为例:
gulimall-search/src/main/java/com/wen/gulimall/search/service/impl/MallSearchServiceImpl.java
/**
* 封装检索结果
* @param searchParam
* @param searchResponse
* @return
*/
private SearchResult buildSearchResponse(SearchParam searchParam, SearchResponse searchResponse) {
SearchResult result = new SearchResult();
// 1. 返回所有查询到的商品
SearchHit[] hits = searchResponse.getHits().getHits();
List<SkuEsModel> skuEsModels = new ArrayList<>();
if(ArrayUtil.isNotEmpty(hits)) {
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
// keyword非空设置高亮
if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();
skuEsModel.setSkuTitle(skuTitle);
}
skuEsModels.add(skuEsModel);
}
}
result.setProducts(skuEsModels);
2. 当前所有商品涉及到的所有属性信息
List<SearchResult.AttrVo> attrVos = new ArrayList<>();
ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
// 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 -> {
return ((Terms.Bucket) item).getKeyAsString();
}).collect(Collectors.toList());
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
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 = searchResponse.getAggregations().get("brand_agg");
for (Terms.Bucket bucket : brand_agg.getBuckets()) {
// 1. 获取品牌的id
long brandId = bucket.getKeyAsNumber().longValue();
// 2. 获取品牌的名字
String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
// 3. 获取平品牌的图片
String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
brandVo.setBrandId(brandId);
brandVo.setBrandName(brandName);
brandVo.setBrandImg(brandImg);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
4. 当前所有商品涉及到的所有分类信息
ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
if(CollectionUtil.isNotEmpty(buckets)){
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(catalogVos);
//
5. 分页信息 - 当前页码
result.setPageNum(searchParam.getPageNum());
6. 分页信息 - 总记录数
long total = searchResponse.getHits().getTotalHits().value;
result.setTotal(total);
7. 分页信息 - 总页数
Integer totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE == 0?(int)total/EsConstant.PRODUCT_PAGESIZE:((int)total/EsConstant.PRODUCT_PAGESIZE + 1);
result.setTotalPages(totalPages);
return result;
}
参数keyword不为空时,skuTitle中keyword的值设置高亮。
1.8 验证结果封装正确性
通过debug查看结果的封装。
1.9 渲染检索页面【P184-192】
(1)修改SearchParam类中库存属性hasStock不给默认值,buildSearchRequest()方法中加上库存判断,有库存,才拼接库存查询。(注意:上面的hasStock和buildSearchRequest()已经修改)
(2)要判断当前location.href是否有参数,没有以?拼接,有以&拼接。可以通过字符串的indexOf()方法进行判断,也可以通过includes()方法进行判断。具体可以参考W3C文档:https://www.w3school.com.cn/jsref/jsref_obj_string.asp
includes() :返回字符串是否包含指定值。
indexOf() :返回值在字符串中第一次出现的位置。
(3)搜索框回显,可以使用 th:value="${param.keyword}"完成回显功能。${param.keyword}可以获取请求参数keyword的值。
(4)分页功能完善,输入页码,点击确定,跳转到具体页面。
面包屑导航:
(1)SearchResult中添加面包屑相关内容
(2)buildSearchResponse()构建查询结果里返回面包屑相关数据
(3)构建属性面包屑导航功能,因为需要属性名,所以需要远程调用gulimall-product服务
1)修改search服务的pom.xml,添加spring-cloud,引入openfeign
2)开启openfeign
3)编写远程调用接口
@GetMapping("/product/attr/info/{attrId}")
public R attrInfo(@PathVariable("attrId") Long attrId);
(5)浏览器对空格的编码和Java不一样,差异化处理:
private String replaceQueryString(SearchParam searchParam, String value, String key) {
String encode = null;
try {
encode = URLEncoder.encode(value, "UTF-8");
// 前端传递过来的空格被解码成+,替换成%20
encode = encode.replace("+","%20"); //浏览器对空格的编码和Java不一样,差异化处理
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = searchParam.get_queryString().replace("&"+key+"=" + encode, "");
return replace;
}
(6)构建品牌面包屑导航功能,因为需要品牌名称,所以需要远程调用gulimall-product服务
1)编写gulimall-product服务所需品牌的接口
@GetMapping("/infos")
public R info(@RequestParam("brandIds") List<Long> brandIds){
List<BrandEntity> brand = brandService.getBrandsByIds(brandIds);
return R.ok().put("brand", brand);
}
2)编写远程调用接口
@GetMapping("/product/brand/infos")
public R brandsInfo(@RequestParam("brandIds") List<Long> brandIds);
(7)注意th:if的优先级比th:with高,可以参照 usingthymeleaf.pdf -》 Attribute Precedence
1.9.1 检索页面完整代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/search/css/index.css">
<link rel="stylesheet" type="text/css" href="/static/search/font/iconfont.css">
<!--<script src="/static/search/js/jquery-3.2.1.min.js"></script>-->
<script src="/static/search/js/jquery-1.12.4.js"></script>
<title>Document</title>
</head>
<body>
<!--头部-->
<div class="header_head">
<div class="header_head_box">
<b class="header_head_p">
<div style="overflow: hidden">
<a href="http://gulimall.com" class="header_head_p_a1" style="width:73px;">
谷粒商城首页
</a>
<a href="#" class="header_head_p_a">
<!--<img src="/static/search/img/img_05.png" style="border-radius: 50%;"/>-->
北京</a>
</div>
<div class="header_head_p_cs">
<a href="#" style="background: #C81623;color: #fff;">北京</a>
<a href="#">上海</a>
<a href="#">天津</a>
<a href="#">重庆</a>
<a href="#">河北</a>
<a href="#">山西</a>
<a href="#">河南</a>
<a href="#">辽宁</a>
<a href="#">吉林</a>
<a href="#">黑龙江</a>
<a href="#">内蒙古</a>
<a href="#">江苏</a>
<a href="#">山东</a>
<a href="#">安徽</a>
<a href="#">浙江</a>
<a href="#">福建</a>
<a href="#">湖北</a>
<a href="#">湖南</a>
<a href="#">广东</a>
<a href="#">广西</a>
<a href="#">江西</a>
<a href="#">四川</a>
<a href="#">海南</a>
<a href="#">贵州</a>
<a href="#">云南</a>
<a href="#">西藏</a>
<a href="#">陕西</a>
<a href="#">甘肃</a>
<a href="#">青海</a>
<a href="#">宁夏</a>
<a href="#">新疆</a>
<a href="#">港澳</a>
<a href="#">台湾</a>
<a href="#">钓鱼岛</a>
<a href="#">海外</a>
</div>
</b>
<ul>
<li>
<a href="#" class="li_2">你好,请登录</a>
</li>
<li>
<a href="#">免费注册</a>
</li>
<span>|</span>
<li>
<a href="#">我的订单</a>
</li>
<span>|</span>
<li class="header_wdjd" style="width:80px;">
<a href="#">我的谷粒商城</a>
<img src="/static/search/image/down-@1x.png" />
<!--<b class="glyphicon glyphicon-menu-down"></b>-->
<div class="header_wdjd_txt">
<ul>
<li>
<a href="#">待处理订单</a>
</li>
<li>
<a href="#">消息</a>
</li>
<li>
<a href="#">返修退换货</a>
</li>
<li>
<a href="#">我的回答</a>
</li>
<li>
<a href="#">降价商品</a>
</li>
<li>
<a href="#">我的关注</a>
</li>
</ul>
<ul>
<li>
<a href="#">我的京豆</a>
</li>
<li>
<a href="#">我的优惠券</a>
</li>
<li>
<a href="#">我的白条</a>
</li>
<li>
<a href="#">我的理财</a>
</li>
</ul>
</div>
</li>
<span>|</span>
<li>
<a href="#">谷粒商城会员</a>
</li>
<span>|</span>
<li>
<a href="#">企业采购</a>
</li>
<span>|</span>
<li class="header_wdjd1">
<a href="#">客户服务</a>
<img src="/static/search/image/down-@1x.png" />
<!--<b class="glyphicon glyphicon-menu-down"></b>-->
<div class="header_wdjd_txt">
<ul>
<p style="width:100%;">客户</p>
<li>
<a href="#">帮助中心</a>
</li>
<li>
<a href="#">售后服务</a>
</li>
<li>
<a href="#">在线客服</a>
</li>
<li>
<a href="#">意见建议</a>
</li>
<li>
<a href="#">电话客服</a>
</li>
<li>
<a href="#">客服邮箱</a>
</li>
<li>
<a href="#">金融资讯</a>
</li>
<li>
<a href="#">售全球客服</a>
</li>
</ul>
<ul>
<p style="width:100%;">商户</p>
<li>
<a href="#">合作招商</a>
</li>
<li>
<a href="#">学习中心</a>
</li>
<li>
<a href="#">商家后台</a>
</li>
<li>
<a href="#">京麦工作台</a>
</li>
<li>
<a href="#">商家帮助</a>
</li>
<li>
<a href="#">规则平台</a>
</li>
</ul>
</div>
</li>
<span>|</span>
<li class="header_wzdh">
<a href="#">网站导航</a>
<img src="/static/search/image/down-@1x.png" />
<!--<b class="glyphicon glyphicon-menu-down"></b>-->
<div class="header_wzdh_txt">
<ul style="width: 25%;">
<p style="width:100%;">特色主题</p>
<li>
<a href="#">谷粒商城试用</a>
</li>
<li>
<a href="#">谷粒商城金融</a>
</li>
<li>
<a href="#">全球售</a>
</li>
<li>
<a href="#">国际站</a>
</li>
<li>
<a href="#">谷粒商城会员</a>
</li>
<li>
<a href="#">谷粒商城预售</a>
</li>
<li>
<a href="#">买什么</a>
</li>
<li>
<a href="#">俄语站</a>
</li>
<li>
<a href="#">装机大师</a>
</li>
<li>
<a href="#">0元评测</a>
</li>
<li>
<a href="#">定期送</a>
</li>
<li>
<a href="#">港澳售</a>
</li>
<li>
<a href="#">优惠券</a>
</li>
<li>
<a href="#">秒杀</a>
</li>
<li>
<a href="#">闪购</a>
</li>
<li>
<a href="#">印尼站</a>
</li>
<li>
<a href="#">谷粒商城金融科技</a>
</li>
<li>
<a href="#">In货推荐</a>
</li>
<li>
<a href="#">陪伴计划</a>
</li>
<li>
<a href="#">出海招商</a>
</li>
</ul>
<ul style="width: 20%;">
<p style="width:100%;">行业频道</p>
<li>
<a href="#">手机</a>
</li>
<li>
<a href="#">智能数码</a>
</li>
<li>
<a href="#">玩3c</a>
</li>
<li>
<a href="#">电脑办公</a>
</li>
<li>
<a href="#">家用电器</a>
</li>
<li>
<a href="#">谷粒商城智能</a>
</li>
<li>
<a href="#">服装城</a>
</li>
<li>
<a href="#">美妆馆</a>
</li>
<li>
<a href="#">家装城</a>
</li>
<li>
<a href="#">母婴</a>
</li>
<li>
<a href="#">食品</a>
</li>
<li>
<a href="#">运动户外</a>
</li>
<li>
<a href="#">农资频道</a>
</li>
<li>
<a href="#">整车</a>
</li>
<li>
<a href="#">图书</a>
</li>
</ul>
<ul style="width: 21%;">
<p style="width:100%;">生活服务</p>
<li>
<a href="#">白条</a>
</li>
<li>
<a href="#">谷粒商城金融App</a>
</li>
<li>
<a href="#">谷粒商城小金库</a>
</li>
<li>
<a href="#">理财</a>
</li>
<li>
<a href="#">智能家电</a>
</li>
<li>
<a href="#">话费</a>
</li>
<li>
<a href="#">水电煤</a>
</li>
<li>
<a href="#">彩票</a>
</li>
<li>
<a href="#">旅行</a>
</li>
<li>
<a href="#">机票酒店</a>
</li>
<li>
<a href="#">电影票</a>
</li>
<li>
<a href="#">谷粒商城到家</a>
</li>
<li>
<a href="#">谷粒商城众测</a>
</li>
<li>
<a href="#">游戏</a>
</li>
</ul>
<ul style="width: 23%; border-right: 0;">
<p style="width:100%;">更多精选</p>
<li>
<a href="#">合作招商</a>
</li>
<li>
<a href="#">谷粒商城通信</a>
</li>
<li>
<a href="#">谷粒商城E卡</a>
</li>
<li>
<a href="#">企业采购</a>
</li>
<li>
<a href="#">服务市场</a>
</li>
<li>
<a href="#">办公生活馆</a>
</li>
<li>
<a href="#">乡村招募</a>
</li>
<li>
<a href="#">校园加盟</a>
</li>
<li>
<a href="#">京友帮</a>
</li>
<li>
<a href="#">谷粒商城社区</a>
</li>
<li>
<a href="#">智能社区</a>
</li>
<li>
<a href="#">游戏社区</a>
</li>
<li>
<a href="#">知识产权维权</a>
</li>
</ul>
</div>
</li>
<span>|</span>
<li class="header_sjjd">
<a href="#">手机谷粒商城</a>
<div class="header_sjjd_div">
<img src="/static/search/img/01.png" />
</div>
</li>
</ul>
</div>
</div>
<!--搜索导航-->
<div class="header_sous">
<div class="logo">
<a href="http://gulimall.com"><img src="/static/search/image/logo1.jpg" alt=""></a>
</div>
<div class="header_form">
<input id="keyword_input" type="text" placeholder="手机" th:value="${param.keyword}"/>
<a href="javascript:searchByKeyword()">搜索</a>
</div>
<div class="header_ico">
<div class="header_gw">
<span><a href="#">我的购物车</a></span>
<img src="/static/search/image/settleup-@1x.png" />
<span>0</span>
</div>
<div class="header_ko">
<p>购物车中还没有商品,赶紧选购吧!</p>
</div>
</div>
<div class="header_form_nav">
<ul>
<li>
<a href="#">谷粒商城之家</a>
</li>
<li>
<a href="#">谷粒商城专卖店</a>
</li>
<li>
<a href="#">平板</a>
</li>
<li>
<a href="#">电脑</a>
</li>
<li>
<a href="#">ipad</a>
</li>
</ul>
</div>
<nav>
<ul>
<li class="nav_li1">
<a href="#">全部商品分类</a>
</li>
<li class="nav_li">
<a href="#">服装城</a>
</li>
<li class="nav_li">
<a href="#">没装馆</a>
</li>
<li class="nav_li">
<a href="#">超市</a>
</li>
<li class="nav_li">
<a href="#">生鲜</a>
</li>
</ul>
<div class="spacer">|</div>
<ul>
<li class="nav_li">
<a href="#">全球购</a>
</li>
<li class="nav_li">
<a href="#">闪购</a>
</li>
<li class="nav_li">
<a href="#">拍卖</a>
</li>
</ul>
<div class="spacer">|</div>
<ul>
<li class="nav_li">
<a href="#">金融</a>
</li>
</ul>
</nav>
<div class="header_main_left">
<ul>
<li>
<a href="#" class="header_main_left_a"><b>家用电器</b></a>
</li>
<li class="header_li2">
<a href="#" class="header_main_left_a"><b>手机</b> / <b>运营商</b> / <b>数码</b></a>
<div class="header_main_left_main">
<div class="header_sj">
<a href="#" class="header_sj_a">玩3c</a>
<a href="#" class="header_sj_a">手机频道</a>
<a href="#" class="header_sj_a">网上营业厅</a>
<a href="#" class="header_sj_a">配件选购中心</a>
<a href="#" class="header_sj_a">企业购</a>
<a href="#" class="header_sj_a">以旧换新</a>
</div>
<ol class="header_ol">
<a href="#" style="color: #111;" class="aaa">手机通讯 ></a>
<li>
<a href="#" style="color: #999;">手机</a>
<a href="#" style="color: #999;">对讲机</a>
<a href="#" style="color: #999;">手机维修</a>
<a href="#" style="color: #999;">以旧换新</a>
</li>
<a href="#" style="color: #111;" class="aaa">运营商 ></a>
<li>
<a href="#" style="color: #999;">合约机</a>
<a href="#" style="color: #999;">固话宽带</a>
<a href="#" style="color: #999;">办套餐</a>
<a href="#" style="color: #999;">从话费/流量</a>
<a href="#" style="color: #999;">中国电信</a>
<a href="#" style="color: #999;">中国移动</a>
<a href="#" style="color: #999;">中国联通</a>
<a href="#" style="color: #999;">谷粒商城通信</a>
<a href="#" style="color: #999;">170选号</a>
</li>
<a href="#" style="color: #111;" class="aaa">手机配件 ></a>
<li style="height: 60px;">
<a href="#" style="color: #999;">手机壳</a>
<a href="#" style="color: #999;">贴膜</a>
<a href="#" style="color: #999;">手机储存卡</a>
<a href="#" style="color: #999;">数据线</a>
<a href="#" style="color: #999;">存电器</a>
<a href="#" style="color: #999;">手机耳机</a>
<a href="#" style="color: #999;">创业配件</a>
<a href="#" style="color: #999;">手机饰品</a>
<a href="#" style="color: #999;">手机电池</a>
<a href="#" style="color: #999;">苹果周边</a>
<a href="#" style="color: #999;">移动电源</a>
<a href="#" style="color: #999;">蓝牙耳机</a>
<a href="#" style="color: #999;">手机支架</a>
<a href="#" style="color: #999;">车载配件</a>
<a href="#" style="color: #999;">拍照配件</a>
</li>
<a href="#" style="color: #111;" class="aaa">摄影摄像 ></a>
<li style="height: 60px;">
<a href="#" style="color: #999;">数码相机</a>
<a href="#" style="color: #999;">单电/微单相机</a>
<a href="#" style="color: #999;">单反相机</a>
<a href="#" style="color: #999;">拍立得</a>
<a href="#" style="color: #999;">运动相机</a>
<a href="#" style="color: #999;">摄像机</a>
<a href="#" style="color: #999;">镜头</a>
<a href="#" style="color: #999;">户外器材</a>
<a href="#" style="color: #999;">影棚器材</a>
<a href="#" style="color: #999;">冲印服务</a>
<a href="#" style="color: #999;">数码相框</a>
</li>
<a href="#" style="color: #111;" class="aaa">数码配件 ></a>
<li style="height: 60px;">
<a href="#" style="color: #999;">三脚架/云台</a>
<a href="#" style="color: #999;">相机包</a>
<a href="#" style="color: #999;">滤镜</a>
<a href="#" style="color: #999;">散光灯/手柄</a>
<a href="#" style="color: #999;">相机清洁</a>
<a href="#" style="color: #999;">机身附件</a>
<a href="#" style="color: #999;">镜头附件</a>
<a href="#" style="color: #999;">读卡器</a>
<a href="#" style="color: #999;">支架</a>
<a href="#" style="color: #999;">电池/存电器</a>
</li>
<a href="#" style="color: #111;" class="aaa">影音娱乐 ></a>
<li>
<a href="#" style="color: #999;">耳机/耳麦</a>
<a href="#" style="color: #999;">音箱/音响</a>
<a href="#" style="color: #999;">智能音箱</a>
<a href="#" style="color: #999;">便携/无线音箱</a>
<a href="#" style="color: #999;">收音机</a>
<a href="#" style="color: #999;">麦克风</a>
<a href="#" style="color: #999;">MP3/MP4</a>
<a href="#" style="color: #999;">专业音频</a>
</li>
<a href="#" style="color: #111;" class="aaa">智能设备 ></a>
<li style="height: 60px;">
<a href="#" style="color: #999;">智能手环</a>
<a href="#" style="color: #999;">智能手表</a>
<a href="#" style="color: #999;">智能眼镜</a>
<a href="#" style="color: #999;">智能机器人</a>
<a href="#" style="color: #999;">运动跟踪器</a>
<a href="#" style="color: #999;">健康监测</a>
<a href="#" style="color: #999;">智能配饰</a>
<a href="#" style="color: #999;">智能家居</a>
<a href="#" style="color: #999;">体感车</a>
<a href="#" style="color: #999;">无人机</a>
<a href="#" style="color: #999;">其他配件</a>
</li>
<a href="#" style="color: #111;" class="aaa">电子教育 ></a>
<li>
<a href="#" style="color: #999;">学生平板</a>
<a href="#" style="color: #999;">点读机</a>
<a href="#" style="color: #999;">早教益智</a>
<a href="#" style="color: #999;">录音笔</a>
<a href="#" style="color: #999;">电纸书</a>
<a href="#" style="color: #999;">电子词典</a>
<a href="#" style="color: #999;">复读机</a>
</li>
</ol>
<div class="header_r">
<div class="header_r_tu">
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
<a href="#"><img src="/static/search/img/56b2f385n8e4eb051.jpg" /></a>
</div>
<div class="header_r_tu1">
<a href="#"><img src="/static/search/img/JD_ash7 - 副本.png" /></a>
<a href="#"><img src="/static/search/img/JD_ash6.png" /></a>
</div>
</div>
</div>
</li>
<li>
<a href="#" class="header_main_left_a"><b>电脑</b> / <b>办公</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>家居</b> / <b>家具</b> / <b>家装</b> / <b>厨具</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>男装</b> / <b>女装</b> / <b>童装</b> / <b>内衣</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>美妆个护 </b>/ <b>宠物</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>女鞋</b> / <b>箱包</b> / <b>钟表</b> / <b>珠宝</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>男鞋</b> / <b>运动</b> / <b>户外</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>汽车</b> / <b>汽车用品</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>母婴</b> / <b>玩具乐器</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>食品</b> / <b>酒类</b> / <b>生鲜</b> / <b>特产</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>礼品鲜花</b> / <b>农资绿植</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>医药保健</b> / <b>计生情趣</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>图书</b> / <b>音箱</b>/ <b>电子书</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>机票</b> / <b>酒店</b> / <b>旅游</b> / <b>生活</b></a>
</li>
<li>
<a href="#" class="header_main_left_a"><b>理财</b> / <b>众筹</b> / <b>白条</b> / <b>保险</b></a>
</li>
</ul>
</div>
</div>
<hr style="border: 1px solid red;margin-top: -7px;">
<!--热卖促销-->
<div class="JD_temai">
<div class="JD_main">
<div class="JD_left">
<div class="hd">
热卖推荐
</div>
<div class="bd mc">
<ul class="mc">
<li>
<a href="#" class="mc_a"><img src="/static/search/img/5a28b5a1n8a5c095f.jpg" alt=""></a>
<div class="mc_div">
<a href="#" class="mc_div_a1">
<em>华为 HUAWEI nova 2S 全面屏四摄 6GB +64GB 曜石黑 移动联通电信4G手机 双卡双待</em>
</a>
<p>
<strong>
<em class="number J-p-5963064">¥2999.00</em>
</strong>
</p>
<a href="#" class="mc_div_a2">立即抢购</a>
</div>
</li>
<li>
<a href="#" class="mc_a"><img src="/static/search/img/59f5eef1n99542494.jpg" alt=""></a>
<div class="mc_div">
<a href="#" class="mc_div_a1">
<em>【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待</em>
</a>
<p>
<strong>
<em class="number J-p-5963064">¥1699.00</em>
</strong>
</p>
<a href="#" class="mc_div_a2">立即抢购</a>
</div>
</li>
<li style="margin-right: 0">
<a href="#" class="mc_a"><img src="/static/search/img/59f5eef1n99542494.jpg" alt=""></a>
<div class="mc_div">
<a href="#" class="mc_div_a1">
<em>华为 HUAWEI nova 2S 全面屏四摄 6GB +64GB 曜石黑 移动联通电信4G手机 双卡双待</em>
</a>
<p>
<strong>
<em class="number J-p-5963064">¥2999.00</em>
</strong>
</p>
<a href="#" class="mc_div_a2">立即抢购</a>
</div>
</li>
</ul>
</div>
</div>
<div class="JD_right">
<div class="hd"> 促销活动</div>
<div class="bd">
<ul>
<li> . <a href="#">红米千元全面屏手机上市</a></li>
<li> . <a href="#">锤子坚果Pro2火爆预约中</a></li>
<li> . <a href="#">大牌新品 疯狂抢购</a></li>
<li> . <a href="#">X20 vivo蓝新色上市</a></li>
<li> . <a href="#">荣耀畅玩7X新品上市</a></li>
</ul>
</div>
</div>
</div>
</div>
<!--手机-->
<div class="JD_ipone">
<div class="JD_ipone_bar">
<div class="JD_ipone_one a">
<a href="#">手机</a>
</div>
<i><img src="/static/search/image/right-@1x.png" alt=""></i>
<div class="JD_ipone_one b">
<a href="#" class="qqq">手机通讯录 <img src="/static/search/image/down-@1x.png" alt=""></a>
<div>
<a href="#">手机通讯</a>
<a href="#">运营商</a>
<a href="#">手机配件</a>
<a href="#">手机服务</a>
</div>
</div>
<i><img src="/static/search/image/right-@1x.png" alt=""></i>
<div class="JD_ipone_one c">
<a href="#" class="qqq">手机 <img src="/static/search/image/down-@1x.png" alt=""></a>
<div>
<a href="#">手机</a>
<a href="#">老人机</a>
<a href="#">对讲机</a>
<a href="#">女性手机</a>
<a href="#">超续航手机</a>
<a href="#">全面屏手机</a>
<a href="#">拍照手机</a>
<a href="#">游戏手机</a>
</div>
</div>
<div class="JD_ipone_one c">
<a th:href="${nav.link}" th:each="nav:${result.navs}"><span th:text="${nav.navName}"></span> : <span th:text="${nav.navValue}"></span>×</a>
</div>
<i><img src="/static/search/image/right-@1x.png" alt=""></i>
</div>
</div>
<!--商品筛选和排序-->
<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" th:with="brandid = ${param.brandId}" >
<!--品牌-->
<div class="JD_nav_wrap" th:if="${#strings.isEmpty(brandid)}">
<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="#" 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="#">
更多
<i style='background: url("static/search/image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("static/search/image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="#">
多选
<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.catalogs}">
<a href="#"
th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}"
th:text="${catalog.catalogName}">5.56英寸及以上</a>
</li>
</ul>
</div>
<div class="sl_ext">
<a href="#">
更多
<i style='background: url("static/search/image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("static/search/image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="#">
多选
<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="#"
th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+val+'")'}"
th:text="${val}">5.56英寸及以上</a>
</li>
</ul>
</div>
</div>
</div>
<div class="JD_show">
<a href="#">
<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="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="#" 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="#" class="number">12466</a>
人评价
</div>
</li>
<li>
<a href="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="#" 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="#" class="number">12466</a>
人评价
</div>
</li>
<li>
<a href="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/593ba628n8794c6a6.jpg" alt=""></a>
<a href="#" 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="#" class="number">15600</a>
人评价
</div>
</li>
<li>
<a href="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/5919637an271a1301.jpg" alt=""></a>
<a href="#" 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="#" 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="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="#">
<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="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/59bf3c47n91d65c73.jpg" alt=""></a>
<a href="#">
<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="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/593ba628n8794c6a6.jpg" alt=""></a>
<a href="#">
<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="#" title="vivo X9s 全网通 4GB+64GB 磨砂黑 移动联通电信4G手机 双卡双待"><img src="/static/search/img/5919637an271a1301.jpg" alt=""></a>
<a href="#">
<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="#"><img src="/static/search/img/599a806bn9d829c1c.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="/static/search/img/593e4de0n5ff878a4.jpg" alt=""></a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!--综合排序-->
<div class="JD_con_right">
<div class="filter">
<!--综合排序-->
<!--saleCount_asc/desc skuPrice_asc/desc -->
<div class="filter_top">
<div class="filter_top_left" th:with="p=${param.sort},priceRange=${param.skuPrice}">
<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="#">综合排序[[${(!#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="#">销量[[${(!#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="#">价格[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') && #strings.endsWith(p,'desc'))?'↓':'↑'}]]</a>
<a class="sort_a" href="#">评论分</a>
<a class="sort_a" href="#">上架时间</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>
</div>
<div class="filter_top_right">
<span class="fp-text">
<b>1</b><em>/</em><i>169</i>
</span>
<a href="#" class="prev"><</a>
<a href="#" 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="#">北京</a>
<a href="#">上海</a>
<a href="#">天津</a>
<a href="#">重庆</a>
<a href="#">河北</a>
<a href="#">山西</a>
<a href="#">河南</a>
<a href="#">辽宁</a>
<a href="#">吉林</a>
<a href="#">黑龙江</a>
<a href="#">内蒙古</a>
<a href="#">江苏</a>
<a href="#">山东</a>
<a href="#">安徽</a>
<a href="#">浙江</a>
<a href="#">福建</a>
<a href="#">湖北</a>
<a href="#">湖南</a>
<a href="#">广东</a>
<a href="#">广西</a>
<a href="#">江西</a>
<a href="#">四川</a>
<a href="#">海南</a>
<a href="#">贵州</a>
<a href="#">云南</a>
<a href="#">西藏</a>
<a href="#">陕西</a>
<a href="#">甘肃</a>
<a href="#">青海</a>
<a href="#">宁夏</a>
<a href="#">新疆</a>
<a href="#">港澳</a>
<a href="#">台湾</a>
<a href="#">钓鱼岛</a>
<a href="#">海外</a>
</div>
<div id="content2">
<a href="#">朝阳区</a>
<a href="#">海淀区</a>
<a href="#">西城区</a>
<a href="#">东城区</a>
<a href="#">大兴区</a>
<a href="#">丰台区</a>
<a href="#">昌平区</a>
<a href="#">顺义区</a>
</div>
<div id="content3">
<a href="#">三环以内</a>
<a href="#">管庄</a>
<a href="#">北苑</a>
<a href="#">定福庄</a>
<a href="#">三环到四环之间</a>
<a href="#">四环到五环之间</a>
<a href="#">五环到六环之间</a>
</div>
</div>
</div>
</div>
<div class="filter_bottom_right">
<ul>
<li>
<a href="#">
<i></i>
谷粒商城配送
</a>
</li>
<li>
<a href="#">
<i></i>
京尊达 </a>
</li>
<li>
<a href="#">
<i></i>
货到付款
</a>
</li>
<li>
<a href="#" th:with="check = ${param.hasStock}">
<input id="showHasStock" type="checkbox" th:checked="${#strings.equals(check,'1')}">
仅显示有货
</a>
</li>
<li>
<a href="#">
<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="#">关注</a>
</div>
<p class="da">
<a href="#" >
<img th:src="${product.skuImg}" class="dim">
</a>
</p>
<ul class="tab_im">
<li><a href="#" 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="#" th:utext="${product.skuTitle}">
Apple iPhone 7 Plus (A1661) 32G 黑色 移动联通电信4G手机
</a>
</p>
<p class="tab_PI">已有<span>11万+</span>热门评价
<a href="#">二手有售</a>
</p>
<p class="tab_CP"><a href="#" 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 class="page_a" th:attr="pn=${result.pageNum - 1}" href="#" 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="#"
th:each="nav:${result.pageNavs}">[[${nav}]]</a>
<a class="page_a" th:attr="pn=${result.pageNum + 1}" href="#" th:if="${result.pageNum<result.totalPages}">
下一页 >
</a>
</span>
<span class="page_span2">
<em>共<b>[[${result.totalPages}]]</b>页 到第</em>
<input class="page_nb" type="number" th:value="${result.pageNum}" min="1" th:max="${result.totalPages}">
<em>页</em>
<a class="page_submit" href="#">确定</a>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--商品精选-->
<div class="JD_jx">
<div class="JD_jx_title">
<div class="mt">
<strong class="mt-title">商品精选</strong>
<img src="/static/search/image/u-ad.gif" alt="">
</div>
<div class="mc">
<ul>
<li>
<div class="mc_img">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<img src="/static/search/img/5a25ffc7N98b35d49.jpg" alt="">
</a>
</div>
<div class="mc_name">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待</em>
</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
<span class="mc_ico" title="购买本商品送赠品">赠品</span>
</div>
<div class="mc_rev">
<a href="#">15930</a>
<span>人好评</span>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<img src="/static/search/img/5a25ffc7N98b35d49.jpg" alt="">
</a>
</div>
<div class="mc_name">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待</em>
</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
<span class="mc_ico" title="购买本商品送赠品">赠品</span>
</div>
<div class="mc_rev">
<a href="#">15930</a>
<span>人好评</span>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<img src="/static/search/img/5a25ffc7N98b35d49.jpg" alt="">
</a>
</div>
<div class="mc_name">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待</em>
</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
<span class="mc_ico" title="购买本商品送赠品">赠品</span>
</div>
<div class="mc_rev">
<a href="#">15930</a>
<span>人好评</span>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<img src="/static/search/img/5a25ffc7N98b35d49.jpg" alt="">
</a>
</div>
<div class="mc_name">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待</em>
</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
<span class="mc_ico" title="购买本商品送赠品">赠品</span>
</div>
<div class="mc_rev">
<a href="#">15930</a>
<span>人好评</span>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<img src="/static/search/img/5a25ffc7N98b35d49.jpg" alt="">
</a>
</div>
<div class="mc_name">
<a href="#" title="【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待">
<em>【预约版】华为 HUAWEI 畅享7S 全面屏双摄 4GB +64GB 黑色 移动联通电信4G手机 双卡双待</em>
</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
<span class="mc_ico" title="购买本商品送赠品">赠品</span>
</div>
<div class="mc_rev">
<a href="#">15930</a>
<span>人好评</span>
</div>
</li>
</ul>
</div>
</div>
</div>
<!--猜你喜欢-->
<div class="JD_cnxh">
<div class="JD_jx_title">
<div class="mt">
<strong class="mt-title">猜你喜欢</strong>
<a href="#">
<img src="/static/search/image/update.png" alt="">
换一批
</a>
</div>
<div class="mc">
<ul>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/59bf3c47n91d65c73.jpg" alt="">
</a>
</div>
<div class="mc_name">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
</div>
<div class="mc_rev">
<a href="#">已有80万+人评价</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/5a28b5c6Ndec5088f.jpg" alt=""></a>
</div>
<div class="mc_name">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
</div>
<div class="mc_rev">
<a href="#">已有80万+人评价</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机"><img src="/static/search/img/593e4de0n5ff878a4.jpg" alt=""></a>
</div>
<div class="mc_name">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
</div>
<div class="mc_rev">
<a href="#">已有80万+人评价</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机"><img src="/static/search/img/593e4de0n5ff878a4.jpg" alt=""></a>
</div>
<div class="mc_name">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
</div>
<div class="mc_rev">
<a href="#">已有80万+人评价</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机"><img src="/static/search/img/59c493a7N3f9b9c85 (1).jpg" alt=""></a>
</div>
<div class="mc_name">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
</div>
<div class="mc_rev">
<a href="#">已有80万+人评价</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机"><img src="/static/search/img/59c493a7N3f9b9c85 (1).jpg" alt=""></a>
</div>
<div class="mc_name">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<em>诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机</em>
</a>
</div>
<div class="mc_rev">
<a href="#">已有80万+人评价</a>
</div>
<div class="mc_price">
<strong>
<span>¥1999.00</span>
</strong>
</div>
</li>
</ul>
</div>
</div>
</div>
<!--我的足迹-->
<div class="JD_zuji">
<div class="JD_jx_title">
<div class="mt">
<strong class="mt-title">我的足迹</strong>
<a href="#">
更多浏览记录
</a>
</div>
<div class="mc">
<ul>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/59e58a11Nc38676d5.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥2998.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/5a28acccN73689386.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥88.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/5a1690ddN441b5dce.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥199.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/5a02bde7N7d4453b1.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥799.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/5a122dbeN044ebf19.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥599.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/59c493a7N3f9b9c85.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥699.00</span>
</strong>
</div>
</li>
<li>
<div class="mc_img">
<a href="#" title="诺基亚 7 (Nokia 7) 4GB+64GB 黑色 全网通 双卡双待 移动联通电信4G手机">
<img src="/static/search/img/5a08f6f6N5bab2c1c.jpg" alt="">
</a>
</div>
<div class="mc_price">
<strong>
<span>¥715.00</span>
</strong>
</div>
</li>
</ul>
</div>
</div>
</div>
<div style="width: 1210px;margin: 0 auto;margin-bottom: 10px"><img src="/static/search/img/5a33a2e0N9a04b4af.jpg" alt=""></div>
<!--底部-->
<footer class="footer">
<div class="footer_top">
<ul>
<li>
<span></span>
<h3>品类齐全,轻松购物</h3>
</li>
<li>
<span></span>
<h3>多仓直发,极速配发</h3>
</li>
<li>
<span></span>
<h3>正品行货,精致服务</h3>
</li>
<li>
<span></span>
<h3>天天低价,畅选无忧</h3>
</li>
</ul>
</div>
<div class="footer_center">
<ol>
<li>购物指南</li>
<li><a href="#" style="color: rgb(114, 114, 114);">购物流程</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">会员介绍</a>
</li>
<li><a href="#">生活旅行</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">常见问题</a>
</li>
<li><a href="#">大家电</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">联系客服</a>
</li>
</ol>
<ol>
<li>配送方式</li>
<li><a href="#" style="color: rgb(114, 114, 114);">上门自提</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">211限时达</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">配送服务查询</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">配送费收取标准</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">海外配送</a>
</li>
</ol>
<ol>
<li>支付方式</li>
<li><a href="#" style="color: rgb(114, 114, 114);">货到付款</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">在线支付</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">分期付款</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">邮局汇款</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">公司转账</a>
</li>
</ol>
<ol>
<li>售后服务</li>
<li><a href="#" style="color: rgb(114, 114, 114);">售后政策</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">价格保护</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">退款说明</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">返修/退换货</a>
</li>
<li><a href="#">取消订单</a>
</li>
</ol>
<ol>
<li>特色服务</li>
<li><a href="#" style="color: rgb(114, 114, 114);">夺宝岛</a>
</li>
<li><a href="#">DIY装机</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">延保服务</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">谷粒商城E卡</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">谷粒商城通信</a>
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">谷粒商城gulimall+</a>
</li>
</ol>
<ol>
<li>谷粒商城自营覆盖区域</li>
<li>
谷粒商城已向全国2661个区县提供自<br> 营配送服务,支持货到付款、
<br> POS机刷卡和售后上门服务。
</li>
<li><a href="#" style="color: rgb(114, 114, 114);">查看详情></a>
</li>
</ol>
</div>
<div class="footer_foot">
<p class="footer_p p1">
<a href="#">关于我们</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">联系我们</a>
<span></span>
<a href="#">联系客服</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">合作招商</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">商家帮助</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">营销中心</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">手机谷粒商城</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">友情链接</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">销售联盟</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">谷粒商城社区</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">风险监测</a>
<span></span>
<a href="#">隐私政策</a>
<span></span>
<a href="#">谷粒商城公益</a>
<span></span>
<a href="#" style="color: rgb(114, 114, 114);">English Site</a>
<span></span>
<a href="#">media & IR</a>
</p>
<p class="footer_p">
<a href="#">京公网安备 11000002000088号</a>
<span></span>
<a href="#">京ICP证070359号</a>
<span></span>
<a href="#">互联网药品信息服务资格证编号(京)-经营性-2014-0008</a>
<span></span>
<a href="#">新出发京零 字第大120007号</a>
</p>
<p class="footer_p">
<a href="#">互联网出版许可证编号新出网证(京)字150号</a>
<span></span>
<a href="#">出版物经营许可证</a>
<span></span>
<a href="#">网络文化经营许可证京网文[2014]2148-348号</a>
<span></span>
<a href="#">违法和不良信息举报电话:4006561155</a>
</p>
<p class="footer_p">
<a href="#">Copyright © 2004 - 2017 谷粒商城JD.com 版权所有</a>
<span></span>
<a href="#">消费者维权热线:4006067733</a>
<a href="#">经营证照</a>
</p>
<p class="footer_p">
<a href="#">谷粒商城旗下网站:</a>
<a href="#">谷粒商城支付</a>
<span></span>
<a href="#">谷粒商城云</a>
</p>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</footer>
<!--右侧侧边栏-->
<div class="header_bar">
<div class="header_bar_box">
<ul>
<li>
<a href="#"><img src="/static/search/img/wo.png" /></a>
<div class="div">
<a href="#">谷粒商城会员</a>
</div>
</li>
<li>
<a href="#"><img src="/static/search/img/gouwuche.png" /></a>
<div class="div">
<a href="#">购物车</a>
</div>
</li>
<li>
<a href="#"><img src="/static/search/img/taoxin.png" /></a>
<div class="div">
<a href="#">我的关注</a>
</div>
</li>
<li>
<a href="#"><img src="/static/search/img/shi.png" /></a>
<div class="div">
<a href="#">我的足迹</a>
</div>
</li>
<li>
<a href="#"><img src="/static/search/img/xinxi.png" /></a>
<div class="div">
<a href="#">我的消息</a>
</div>
</li>
<li>
<a href="#"><img src="/static/search/img/qianbao.png" /></a>
<div class="div">
<a href="#">资讯JIMI</a>
</div>
</li>
</ul>
<ul>
<li>
<a href="#"><img src="/static/search/img/fa3f24a70d38bd439261cb7439e517a5.png" /></a>
<div class="div">
<a href="#">顶部</a>
</div>
</li>
<li>
<a href="#"><img src="/static/search/img/xinxi.png" /></a>
<div class="div">
<a href="#">反馈</a>
</div>
</li>
</ul>
</div>
</div>
<script>
$(".sl_ext a:nth-child(1)").hover(function(){
$(this).children("b").stop(true).animate({top:"3px"},50);
$(this).children("i").stop(true).animate({top:"-23px"},50)
},function(){
$(this).children("b").stop(true).animate({top:"24px"},50);
$(this).children("i").stop(true).animate({top:"3px"},50)
});
$(".sl_ext a:nth-child(2)").hover(function(){
$(this).children("span").stop(true).animate({top:"-1px"},100);
$(this).children("i").stop(true).animate({top:"-14px"},100).css({display:"none"})
},function(){
$(this).children("span").stop(true).animate({top:"14px"},100);
$(this).children("i").stop(true).animate({top:"-1px"},100).css({display:"block"})
});
$('.tab_im img').hover(function(){
var a=$(this).prop('src');
var index=$(this).parents('li').index();
$(this).parents('li').css('border','2px solid red').siblings('li').css('border','1px solid #ccc');
$(this).parents('ul').prev().find('img').prop('src',a);
$(this).parents('ul').siblings('.tab_JE').find('a').eq(list).css('display','block').siblings('a').css('display','none');
$(this).parents('ul').siblings('.tab_R').find('span').eq(list).css('display','block').siblings('span').css('display','none')
});
$(".JD_ipone_one").hover(function(){
$(this).children("div").css({display:"block"})
},function(){
$(this).children("div").css({display:"none"})
});
$("#tab>li").click(function() {
var i = $(this).index();
$("#container>div").hide().eq(i).show()
});
$(".dizhi_show").hover(function(){
$(".dizhi_con").css({display:"block"})
},function(){
$(".dizhi_con").css({display:"none"})
});
$(".dizhi_con").hover(function(){
$(this).css({display:"block"})
},function(){
$(this).css({display:"none"})
});
//显示隐藏
var $li = $(".JD_nav_logo>div:gt(3)").hide();
$('.JD_show span').click(function(){
if($li.is(':hidden')){
$li.show();
$(this).css({width:"86px"}).text('收起 ^');
}else{
$li.hide();
$('.JD_show span').css({width:"291px"}).text('更多选项( CPU核数、网络、机身颜色 等)');
}
return false;
});
$(".rig_tab>div").hover(function(){
var i = $(this).index();
$(this).find('.ico').css({display:'block'}).stop(true).animate({top:"190px"},300)
},function(){
var i = $(this).index();
$(this).find('.ico').css({display:'none'}).stop(true).animate({top:"230px"})
});
$('.header_main_left>ul>li').hover(function() {
$(this).css({
background: "#f0f0f0"
}).find('.header_main_left_main').stop(true).fadeIn(300)
}, function() {
$(this).css({
background: "#fff"
}).find(".header_main_left_a").css({
color: "#000"
});
$(this).find('.header_main_left_main').stop(true).fadeOut(100)
});
$(".header_sj a").hover(function() {
$(this).css({
background: "#444"
})
}, function() {
$(this).css({
background: "#6e6568"
})
});
$(".nav_li1 a").hover(function(){
$(".header_main_left").stop(true).fadeIn()
},function(){
$(".header_main_left").stop(true).fadeOut()
});
$(".header_main_left").hover(function(){
$(this).stop(true).fadeIn()
},function(){
$(this).stop(true).fadeOut()
});
//右侧侧边栏
$(".header_bar_box ul li").hover(function() {
$(this).css({
background: "#7A6E6E"
}).children(".div").css({
display: "block"
}).stop(true).animate({
left: "-60px"
}, 300)
}, function() {
$(this).css({
background: "#7A6E6E"
}).children(".div").css({
display: "none"
}).stop(true).animate({
left: "0"
}, 300)
});
//底部
$(".footer_foot .p1 a").hover(function(){
$(this).css("color","#D70B1C")
},function(){
$(this).css("color","#727272")
});
$(".footer .footer_center ol li a").hover(function(){
$(this).css("color","#D70B1C")
},function(){
$(this).css("color","#727272")
})
function searchProducts(name,value){
// 原来的页面
// var href = location.href + "";
// if(href.includes("?")){
// location.href = location.href + "&"+name+"="+value;
// }else {
// location.href = location.href + "?"+name+"="+value;
// }
location.href = replaceAndAddParamVal(location.href,name,value,true)
}
function searchByKeyword(){
searchProducts("keyword",$('#keyword_input').val());
}
$(".page_a").click(function (){
var pn = $(this).attr("pn");
var href = location.href;
// if(href.includes("pageNum")){
// 替换pageNum
location.href = replaceAndAddParamVal(href,"pageNum",pn);
// }else {
// location.href = location.href + "&pageNum="+pn;
// }
return false;
})
function replaceAndAddParamVal(url,paramName,replaceVal,forceAdd){
var oUrl = url.toString();
// 没有就添加,有就替换
if(oUrl.includes(paramName)){
if(forceAdd){
var nUrl = "";
if(oUrl.includes("?")){
nUrl = oUrl + "&" + paramName+'='+replaceVal;
}else {
nUrl = oUrl + "?" + paramName+'='+replaceVal;
}
return nUrl;
}else {
var re = eval('/('+paramName+'=)([^&]*)/gi');
var nUrl = oUrl.replace(re,paramName+'='+replaceVal)
return nUrl;
}
}else {
var nUrl = "";
if(oUrl.includes("?")){
nUrl = oUrl + "&" + paramName+'='+replaceVal;
}else {
nUrl = oUrl + "?" + paramName+'='+replaceVal;
}
return nUrl;
}
}
// 分页 - 点击确定,跳转到指定页面
$('.page_submit').click(function (){
var pageNum = $('.page_nb').val();
var href = location.href;
// if(href.includes("pageNum")){
// 替换pageNum
location.href = replaceAndAddParamVal(href,"pageNum",pageNum);
// }else {
// location.href = location.href + "&pageNum="+pageNum;
// }
return false;
})
// 排序
$('.sort_a').click(function (){
// 1. 当前被点击的元素变为选中状态
// 改变当前元素及兄弟元素的样式
// changeStyle(this);
$(this).toggleClass("desc"); //对当前被选中的元素进行进行class属性的desc隐藏或显示
// 2. 跳转到指定位置 sort=saleCount_asc/desc
var sort = $(this).attr("sort");
sort = $(this).hasClass("desc")?sort+"_desc":sort+"_asc";
location.href = replaceAndAddParamVal(location.href,"sort",sort);
// 禁止默认行为,a标签的跳转行为
return false;
})
function changeStyle(ele){
// '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 (){
var text = $(this).text().replace("↓","").replace("↑","");
$(this).text(text);
});
$(ele).css({"color":"#FFF","border-color":"#e4393c","background":"#e4393c"});
// 改变升降序 toggleClass-对当前被选中的元素进行进行class属性的desc隐藏或显示
$(ele).toggleClass("desc");// 加上就是降序,不加是升序
if($(ele).hasClass("desc")){
// 降序
var text = $(ele).text().replace("↓","").replace("↑","");
text = text + "↓";
$(ele).text(text);
}else {
// 升序
var text = $(ele).text().replace("↓","").replace("↑","");
text = text + "↑";
$(ele).text(text);
}
}
$('#skuPriceSearchBtn').click(function (){
// 1. 拼上价格区间的查询条件
var from = $('#skuPriceFrom').val();
var to = $('#skuPriceTo').val();
var query = from + '_'+to;
location.href = replaceAndAddParamVal(location.href,"skuPrice",query);
})
$('#showHasStock').change(function (){
if($(this).prop('checked')){
location.href = replaceAndAddParamVal(location.href,"hasStock",1);
}else {
// 没选中
var re = eval('/(hasStock=)([^&]*)/gi');
// var re = eval('/(&hasStock=)([^&]*)/gi');
location.href = (location.href+"").replace(re,"");
}
})
</script>
</body>
</html>
1.9.2 检索服务后端相关代码
1.9.2.1 引入依赖
用于面包屑导航,远程调用商品服务,根据ID获取名称。注意要引入spring-cloud依赖管理,以及版本控制。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.9.2.2 vo
包 gulimall-search/src/main/java/com/wen/gulimall/search/vo/
@Data
public class AttrResponseVo {
private Long attrId;
/**
* 属性名
*/
private String attrName;
/**
* 是否需要检索[0-不需要,1-需要]
*/
private Integer searchType;
/**
* 属性图标
*/
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;
/**
* 值类型[0-为单个值,1-可以选择多个值]
*/
private Integer valueType;
/**
* 所属分组
*/
private Long attrGroupId;
/**
* 所属分组名称
*/
private String groupName;
/**
* 所属分类名称
*/
private String catelogName;
/**
* 所属分类的路径
*/
private Long[] catelogPath;
}
@Data
public class BrandVo {
/**
* "brandId": 0,
* "brandName": "string",
*/
private Long brandId;
private String name;
}
@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=0/1
* skuPrice=1_500/_500/500_
* bandId=1
* attrs=2_5寸:6寸
*/
private Integer hasStock; // 是否只显示有货,0(无库存)1(有库存)
private String skuPrice;// 价格区间查询
private List<Long> brandId;// 按照品牌进行查询,可以多选
private List<String> attrs;// 按照属性进行筛选
private Integer pageNum = 1;// 页码
private String _queryString; // 原生的所有查询条件
}
@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;
}
}
1.9.2.3 controller
@Controller
public class SearchController {
@Resource
private MallSearchService mallSearchService;
/**
* 自动将页面提交过来的所有请求查询参数封装成指定的对象
* @param searchParam
* @return
*/
@GetMapping("/list.html")
public String listPage(SearchParam searchParam, Model model, HttpServletRequest request){
searchParam.set_queryString(request.getQueryString());
// 1. 根据传递来的页面的查询参数,去es中检索商品
SearchResult result = mallSearchService.search(searchParam);
model.addAttribute("result", result);
return "list";
}
}
1.9.2.4 service
@Service
public class MallSearchServiceImpl implements MallSearchService {
@Resource
private RestHighLevelClient restHighLevelClient;
@Resource
private ProductFeignService productFeignService;
// 根据条件去es中检索
@Override
public SearchResult search(SearchParam searchParam) {
// 动态的构建出查询所需要的DSL
SearchResult result = null;
// 1. 准备检索请求
SearchRequest searchRequest = buildSearchRequest(searchParam);
try {
// 2. 执行检索请求
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
// 3. 分析响应数据,封装成需要的格式
result = buildSearchResponse(searchParam,searchResponse);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 准备检索请求
* # 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
* @return
*/
private SearchRequest buildSearchRequest(SearchParam searchParam) {
// 构建DSL语句对象
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* 查询:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
*/
// 1. 构建bool - query
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 1.1 bool - must 模糊匹配
if(StrUtil.isNotEmpty(searchParam.getKeyword())){
boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
}
// 1.2 bool - filter - 按照三级分类id查询
if(searchParam.getCatalog3Id() != null){
boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
}
// 1.2 bool -filter - 按照品牌id查询
if(CollectionUtil.isNotEmpty(searchParam.getBrandId())){
boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
}
// 1.2 bool - filter - 按照所有指定的属性进行查询
if(CollectionUtil.isNotEmpty(searchParam.getAttrs())){
// attrs=1_5寸:6寸&attrs=2_8G:16G
for (String attrStr : searchParam.getAttrs()) {
String[] s = attrStr.split("_");
String attrId = s[0]; // 检索的属性id
String[] attrValues = s[1].split(":"); // 这个属性检索所需要的值
BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
// ScoreMode.None 不参与评分
// 每一个必须都得生成一个nested查询
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
boolQuery.filter(nestedQuery);
}
}
// 1.2 bool -filter - 按照库存是否有进行查询
if(searchParam.getHasStock() != null) {
boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
}
// 1.2 bool -filter - 按照价格区间
if(StrUtil.isNotEmpty(searchParam.getSkuPrice())){
String[] s = searchParam.getSkuPrice().split("_");
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
if(s.length == 2){
rangeQuery.gte(s[0]).lte(s[1]);
boolQuery.filter(rangeQuery);
}else if(s.length == 1){
if(searchParam.getSkuPrice().startsWith("_")){
rangeQuery.lte(s[0]);
boolQuery.filter(rangeQuery);
}
if(searchParam.getSkuPrice().endsWith("_")){
rangeQuery.gte(s[0]);
boolQuery.filter(rangeQuery);
}
}
}
// 把以上所有的条件都拿来进行封装
sourceBuilder.query(boolQuery);
/**
* 排序,分页,高亮
*/
// 2.1 排序
if(StrUtil.isNotEmpty(searchParam.getSort())){
// sort=saleCount_asc/desc
String[] s = searchParam.getSort().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 form:5 size:5
// from = (pageNum - 1)*size
sourceBuilder.from((searchParam.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
// 2.3 高亮
if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
HighlightBuilder builder = new HighlightBuilder();
builder.field("skuTitle");
builder.preTags("<b style='color:red'>");
builder.postTags("</b>");
sourceBuilder.highlighter(builder);
}
/**
* 聚合分析
*/
// 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);
// 2. 分类聚合 catalog_agg
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
// 分类聚合的子聚合
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
// TODO 1.聚合catalog
sourceBuilder.aggregation(catalog_agg);
// 3. 属性聚合
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
// 聚合出当前所有的attrId
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(10);
// 聚合出当前所有的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));
attr_agg.subAggregation(attr_id_agg);
// TODO 1.聚合attr
sourceBuilder.aggregation(attr_agg);
System.out.println("构建的DSL语句"+sourceBuilder.toString());
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},sourceBuilder);
return searchRequest;
}
/**
* 封装检索结果
* @param searchParam
* @param searchResponse
* @return
*/
private SearchResult buildSearchResponse(SearchParam searchParam, SearchResponse searchResponse) {
SearchResult result = new SearchResult();
// 1. 返回所有查询到的商品
SearchHit[] hits = searchResponse.getHits().getHits();
List<SkuEsModel> skuEsModels = new ArrayList<>();
if(ArrayUtil.isNotEmpty(hits)) {
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
// keyword非空设置高亮
if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();
skuEsModel.setSkuTitle(skuTitle);
}
skuEsModels.add(skuEsModel);
}
}
result.setProducts(skuEsModels);
2. 当前所有商品涉及到的所有属性信息
List<SearchResult.AttrVo> attrVos = new ArrayList<>();
ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
// 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 -> {
return ((Terms.Bucket) item).getKeyAsString();
}).collect(Collectors.toList());
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
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 = searchResponse.getAggregations().get("brand_agg");
for (Terms.Bucket bucket : brand_agg.getBuckets()) {
// 1. 获取品牌的id
long brandId = bucket.getKeyAsNumber().longValue();
// 2. 获取品牌的名字
String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
// 3. 获取平品牌的图片
String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
brandVo.setBrandId(brandId);
brandVo.setBrandName(brandName);
brandVo.setBrandImg(brandImg);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
4. 当前所有商品涉及到的所有分类信息
ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
if(CollectionUtil.isNotEmpty(buckets)){
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(catalogVos);
//
5. 分页信息 - 当前页码
result.setPageNum(searchParam.getPageNum());
5. 分页信息 - 总记录数
long total = searchResponse.getHits().getTotalHits().value;
result.setTotal(total);
5. 分页信息 - 总页数
Integer totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE == 0?(int)total/EsConstant.PRODUCT_PAGESIZE:((int)total/EsConstant.PRODUCT_PAGESIZE + 1);
result.setTotalPages(totalPages);
5. 分页信息 - 导航栏
List<Integer> pageNavs = new ArrayList<>();
for(int i = 1;i<=totalPages;i++){
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
// 6. 构建面包屑导航功能 - 属性
if(CollectionUtil.isNotEmpty(searchParam.getAttrs())) {
List<SearchResult.NavVo> collect = searchParam.getAttrs().stream().map(attr -> {
// 1. 分析每一个参数传过来的参数值
SearchResult.NavVo navVo = new SearchResult.NavVo();
// attrs=1_5寸:6寸
String[] s = attr.split("_");
navVo.setNavValue(s[1]);
R r = productFeignService.attrInfo(Long.parseLong(s[0]));
result.getAttrIds().add(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. 取消了这个面包屑以后,我们要跳转到哪个地方
String replace = replaceQueryString(searchParam, attr, "attrs");
navVo.setLink("http://search.gulimall.com/list.html?" + replace);
return navVo;
}).collect(Collectors.toList());
result.setNavs(collect);
}
// 品牌、分类的面包屑
if(CollectionUtil.isNotEmpty(searchParam.getBrandId())){
List<SearchResult.NavVo> navs = result.getNavs();
SearchResult.NavVo navVo = new SearchResult.NavVo();
navVo.setNavName("品牌");
// TODO 远程查询所有品牌
R r = productFeignService.brandsInfo(searchParam.getBrandId());
if(r.getCode() == 0){
List<BrandVo> brand = r.getData("brand", new TypeReference<List<BrandVo>>() {
});
StringBuffer stringBuffer = new StringBuffer();
String replace = "";
for (BrandVo brandVo : brand) {
stringBuffer.append(brandVo.getName()+";");
replace = replaceQueryString(searchParam,brandVo.getBrandId()+"","brandId");
}
navVo.setNavValue(stringBuffer.toString());
navVo.setLink("http://search.gulimall.com/list.html?" + replace);
}
navs.add(navVo);
}
return result;
}
private String replaceQueryString(SearchParam searchParam, String value, String key) {
String encode = null;
try {
encode = URLEncoder.encode(value, "UTF-8");
// 前端传递过来的空格被解码成+,替换成%20
encode = encode.replace("+","%20"); //浏览器对空格的编码和Java不一样,差异化处理
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = searchParam.get_queryString().replace("&"+key+"=" + encode, "");
return replace;
}
}
1.9.2.5 远程调用接口
开启openfeign
@EnableFeignClients // 开启openfeign
接口
gulimall-search/src/main/java/com/wen/gulimall/search/feign/ProductFeignService.java
@FeignClient("gulimall-product")
public interface ProductFeignService {
@GetMapping("/product/attr/info/{attrId}")
public R attrInfo(@PathVariable("attrId") Long attrId);
@GetMapping("/product/brand/infos")
public R brandsInfo(@RequestParam("brandIds") List<Long> brandIds);
}
1.9.3 远程服务相关接口
1.9.3.1 attrInfo接口
gulimall-product/src/main/java/com/wen/gulimall/product/app/AttrController.java
/**
* 修改回显信息
*/
@RequestMapping("/info/{attrId}")
public R info(@PathVariable("attrId") Long attrId){
//AttrEntity attr = attrService.getById(attrId);
AttrRespVo attrRespVo = attrService.getAttrInfo(attrId);
return R.ok().put("attr", attrRespVo);
}
gulimall-product/src/main/java/com/wen/gulimall/product/service/AttrService.java
AttrRespVo getAttrInfo(Long attrId);
gulimall-product/src/main/java/com/wen/gulimall/product/service/impl/AttrServiceImpl.java
@Cacheable(value = "attr",key = "'attrinfo:'+#root.args[0]")
@Override
public AttrRespVo getAttrInfo(Long attrId) {
AttrEntity attrEntity = this.getById(attrId);
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
if (attrEntity.getCatelogId() != null) {
// 所属分类
Long[] catelogPath = categoryService.findCatelogPath(attrEntity.getCatelogId());
attrRespVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
}
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
// 所属分组
AttrAttrgroupRelationEntity attrAttrgroupRelation = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
// 可能属性与属性分组没有关联关系
if (attrAttrgroupRelation != null) {
attrRespVo.setAttrGroupId(attrAttrgroupRelation.getAttrGroupId());
AttrGroupEntity groupEntity = attrGroupDao.selectById(attrAttrgroupRelation.getAttrGroupId());
if (groupEntity != null) {
attrRespVo.setGroupName(groupEntity.getAttrGroupName());
}
}
}
return attrRespVo;
}
1.9.3.2 brandsInfo接口
gulimall-product/src/main/java/com/wen/gulimall/product/app/BrandController.java
@GetMapping("/infos")
public R info(@RequestParam("brandIds") List<Long> brandIds){
List<BrandEntity> brand = brandService.getBrandsByIds(brandIds);
return R.ok().put("brand", brand);
}
gulimall-product/src/main/java/com/wen/gulimall/product/service/BrandService.java
List<BrandEntity> getBrandsByIds(List<Long> brandIds);
gulimall-product/src/main/java/com/wen/gulimall/product/service/impl/BrandServiceImpl.java
@Override
public List<BrandEntity> getBrandsByIds(List<Long> brandIds) {
return baseMapper.selectList(new QueryWrapper<BrandEntity>().in("brand_id",brandIds));
}