前言
不必糾結當下,也不必太擔憂未來,
人生沒有無用的經歷,
所以,一直走,天一定亮
173~202
6.商城業務-檢索服務
6.1檢索服務-搭建環境頁面
把搜尋的靜態頁面複製到gulimall-search
下的src/main/resources/templates
修改html
宣告和thymeleaf
名稱空間
修改index.html
裡的靜態資源地址
href="
替換為
href="/static/search/
src="
替換為
src="/static/search/
上傳靜態資源到nginx
的/root/mall/nginx/html/static/search
匯入thymeleaf
依賴
<!-- 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
管理員執行SwicthHosts
,配置hosts
檔案,配置搜尋頁轉發地址
192.168.188.180 search.gulimall.com
配置/root/mall/nginx/conf/conf.d/gulimall.conf
server_name *.gulimall.com;
然後重啟nginx
docker restart nginx
配置閘道器
- id: gulimall_search_route
uri: lb://gulimall-search
predicates:
- Host=search.gulimall.com
訪問http://search.gulimall.com/
6.2檢索服務-調整頁面跳轉
配置nginx
配置gulimall.conf
server_name gulimall.com *.gulimall.com;
然後重啟nginx
docker restart nginx
配置thymeleaf
spring:
thymeleaf:
cache: false
安裝devtools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
首頁跳轉
# 第一處
<a href="http://gulimall.com" class="header_head_p_a1" style="width:73px;">
穀粒商城首頁
</a>
# 第二處
<div class="logo">
<a href="http://gulimall.com"><img src="/static/search/./image/logo1.jpg" alt=""></a>
</div>
二級分類跳轉
重新命名index.html
為list.html
,並且新增SearchController
@Controller
public class SearchController {
@GetMapping(value = "/list.html")
public String listPage(){
return "list";
}
}
修改catalogLoader.js
var cata3link = $("<a href=\"http://search.gulimall.com/list.html?catalog3Id="+ctg3.id+"\" style=\"color: #999;\">" + ctg3.name + "</a>");
搜尋跳轉
配置gulimall-product
的src/main/resources/templates/index.html
<a href="javascript:search();" ><img src="/static/index/img/img_09.png" /></a>
6.3檢索服務-檢索查詢引數模型分析抽取
查詢引數模型:
keyword
:頁面傳遞過來的全文匹配關鍵字brandId
:List<Long>
,品牌id,可以多選catalog3Id
:三級分類idsort
:排序條件:sort=price/salecount/hotscore_desc/asc
hasStock
:是否顯示有貨skuPrice
:價格區間查詢attrs
:按照屬性進行篩選pageNum
:頁碼_queryString
:原生的所有查詢條件
public class SearchParam {
/**
* 頁面傳遞過來的全文匹配關鍵字
*/
private String keyword;
/**
* 品牌id,可以多選
*/
private List<Long> brandId;
/**
* 三級分類id
*/
private Long catalog3Id;
/**
* 排序條件:sort=price/salecount/hotscore_desc/asc
*/
private String sort;
/**
* 是否顯示有貨
*/
private Integer hasStock;
/**
* 價格區間查詢
*/
private String skuPrice;
/**
* 按照屬性進行篩選
*/
private List<String> attrs;
/**
* 頁碼
*/
private Integer pageNum = 1;
/**
* 原生的所有查詢條件
*/
private String _queryString;
}
6.4檢索服務-檢索返回結果模型分析抽取
返回結果模型:
-
product
:List<SkuEsModel>
,查詢到的所有商品資訊 -
pageNum
:當前頁碼 -
total
:總記錄數 -
totalPages
:總頁碼 -
pageNavs
: -
brands
:List<BrandVo>
,當前查詢到的結果,所有涉及到的品牌brandId
:品牌IdbrandName
:品牌名稱brandImg
:品牌圖片
-
attrs
:List<AttrVo>
,當前查詢到的結果,所有涉及到的所有屬性attrId
:屬性IdattrName
:屬性名稱attrValue
:屬性值,可能是多個
-
catalogs
:List<CatalogVo>
,當前查詢到的結果,所有涉及到的所有分類catalogId
:分類IdcatalogName
:分類名稱
-
navs
:List<NavVo>
,麵包屑導航資料navName
:名稱navValue
:值link
:連結
@Data
public class SearchResult {
/**
* 查詢到的所有商品資訊
*/
private List<SkuEsModel> product;
/**
* 當前頁碼
*/
private Integer pageNum;
/**
* 總記錄數
*/
private Long total;
/**
* 總頁碼
*/
private Integer totalPages;
private List<Integer> pageNavs;
/**
* 當前查詢到的結果,所有涉及到的品牌
*/
private List<BrandVo> brands;
/**
* 當前查詢到的結果,所有涉及到的所有屬性
*/
private List<AttrVo> attrs;
/**
* 當前查詢到的結果,所有涉及到的所有分類
*/
private List<CatalogVo> catalogs;
//===========================以上是返回給頁面的所有資訊============================//
/* 麵包屑導航資料 */
private List<NavVo> navs;
@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 AttrVo {
private Long attrId;
private String attrName;
private List<String> attrValue;
}
@Data
public static class CatalogVo {
private Long catalogId;
private String catalogName;
}
}
6.5檢索服務-檢索DSL測試-查詢部分
這個查詢是在名為 product
的索引中查詢 skuTitle
包含 "華為",catalogId
為 225,brandId
為 1、2 或 3,巢狀屬性 attrs.attrId
為 "1" 且 attrs.attrValue
為 "華為 Mate60 Pro" 或 "2023",並且 hasStock
為 "true",skuPrice
在 0 到 6000 之間的商品。結果按 skuPrice
降序排序,並返回第1個結果。同時,高亮顯示 skuTitle
中的匹配部分。
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "華為"
}
}
],
"filter": [
{
"term": {
"catalogId": 225
}
},
{
"terms": {
"brandId": [
1,
2,
3
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "1"
}
}
},
{
"terms": {
"attrs.attrValue": [
"華為 Mate60 Pro",
"2023"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 1,
"size": 1,
"highlight": {
"fields": {
"skuTitle": {}
},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
}
}
6.6檢索服務-檢索DSL測試-聚合部分
聚合brandName
、brandImg
catelogName
attrName
欄位時失敗了
之前在對映中將 brandName
、brandImg
catelogName
attrName
欄位設定為 keyword
型別,但禁用了 index
和 doc_values
。這意味著這些欄位不能用於搜尋、排序或聚合。如果你需要在這些欄位上進行聚合操作,需要確保這些欄位啟用了 doc_values
。
為了保留資料需要進行資料遷移
先查詢product
對映
GET product
然後在修改,刪除
"index": false,
"doc_values": false
以下是完整程式碼
PUT gulimall_product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "long"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword"
},
"saleCount": {
"type": "long"
},
"hosStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catelogId": {
"type": "long"
},
"brandName": {
"type": "keyword"
},
"brandImg": {
"type": "keyword"
},
"catalogName": {
"type": "keyword"
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword"
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
資料遷移
POST _reindex
{
"source": {
"index": "product"
},
"dest": {
"index": "gulimall_product"
}
}
資料遷移成功,我們要修改程式碼裡的es索引名稱
這個查詢是在名為 gulimall_product
的索引中查詢 skuTitle
包含 "手機",catalogId
為 "225",brandId
為 "3" 或 "5",巢狀屬性 attrs.attrId
為 "6" 且 attrs.attrValue
為 "海思(Hisilicon)" 或 "以官網資訊為準",並且 hasStock
為 "true",skuPrice
在 0 到 7000 之間的商品。結果按 skuPrice
降序排序,並返回前2個結果。同時,高亮顯示 skuTitle
中的匹配部分。聚合部分包括品牌、目錄和屬性的相關資訊。
GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "手機"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"3",
"5"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "6"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"以官網資訊為準"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 7000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 2,
"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
}
}
}
}
}
}
}
}
6.7檢索服務-SearchRequest構建-檢索
主要步驟:
-
1.準備檢索請求
- 構建
bool-query
:BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();
bool-must
:skuTitle
商品名稱查詢bool-fiter
:boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()))
:catalogId
三級分類查詢boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()))
:brandId
品牌查詢attrs
基礎屬性查詢boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1))
:hasStock
庫存查詢skuPrice
商品價格區間查詢
- 構建
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/**
* 模糊匹配,過濾(按照屬性,分類,品牌,價格區間,庫存)
*/
//1. 構建bool-query
BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();
//1.1 bool-must
if(!StringUtils.isEmpty(param.getKeyword())){
boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
}
//1.2 bool-fiter
//1.2.1 catelogId
if(null != param.getCatalog3Id()){
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
}
//1.2.2 brandId
if(null != param.getBrandId() && param.getBrandId().size() >0){
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
}
//1.2.3 attrs
if(param.getAttrs() != null && param.getAttrs().size() > 0){
param.getAttrs().forEach(item -> {
//attrs=1_5寸:8寸&2_16G:8G
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//attrs=1_5寸:8寸
String[] s = item.split("_");
String attrId=s[0];
String[] attrValues = s[1].split(":");//這個屬性檢索用的值
boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
});
}
//1.2.4 hasStock
if(null != param.getHasStock()){
boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));
}
//1.2.5 skuPrice
if(!StringUtils.isEmpty(param.getSkuPrice())){
//skuPrice形式為:1_500或_500或500_
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
String[] price = param.getSkuPrice().split("_");
if(price.length==2){
rangeQueryBuilder.gte(price[0]).lte(price[1]);
}else if(price.length == 1){
if(param.getSkuPrice().startsWith("_")){
rangeQueryBuilder.lte(price[1]);
}
if(param.getSkuPrice().endsWith("_")){
rangeQueryBuilder.gte(price[0]);
}
}
boolQueryBuilder.filter(rangeQueryBuilder);
}
//封裝所有的查詢條件
searchSourceBuilder.query(boolQueryBuilder);
6.8檢索服務-SearchRequest構建-排序、分頁、高亮&測試
主要步驟:
-
排序:
searchSourceBuilder.sort(sortFileds[0],sortOrder);
-
分頁
searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
:頁碼searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
:每頁條數
-
高亮
HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("skuTitle"); highlightBuilder.preTags("<b style='color:red'>"); highlightBuilder.postTags("</b>"); searchSourceBuilder.highlighter(highlightBuilder);
-
測試:
/**
* 排序,分頁,高亮
*/
//排序
//形式為sort=hotScore_asc/desc
if(!StringUtils.isEmpty(param.getSort())){
String sort = param.getSort();
String[] sortFileds = sort.split("_");
SortOrder sortOrder="asc".equalsIgnoreCase(sortFileds[1])?SortOrder.ASC:SortOrder.DESC;
searchSourceBuilder.sort(sortFileds[0],sortOrder);
}
//分頁
searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
//高亮
if(!StringUtils.isEmpty(param.getKeyword())){
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
searchSourceBuilder.highlighter(highlightBuilder);
}
測試http://localhost:8208/list.html?keyword=華為&brandId=2&catalog3Id=225&sort=skuPrice_asc&hasStock=1&skuPrice=0_7000
把構建的語句列印出來
在kibana
執行列印出來的查詢語句,能夠查詢出來即可
6.9檢索服務-SearchRequest構建-聚合
主要步驟:
- 品牌聚合
- 分類聚合
- 屬性資訊聚合
程式碼
/**
* 聚合分析
*/
//1. 按照品牌進行聚合
TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
brand_agg.field("brandId").size(50);
//1.1 品牌的子聚合-品牌名聚合
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
.field("brandName").size(1));
//1.2 品牌的子聚合-品牌圖片聚合
brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
.field("brandImg").size(1));
searchSourceBuilder.aggregation(brand_agg);
//2. 按照分類資訊進行聚合
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
catalog_agg.field("catalogId").size(20);
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
searchSourceBuilder.aggregation(catalog_agg);
//2. 按照屬性資訊進行聚合
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
//2.1 按照屬性ID進行聚合
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
attr_agg.subAggregation(attr_id_agg);
//2.1.1 在每個屬性ID下,按照屬性名進行聚合
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
//2.1.1 在每個屬性ID下,按照屬性值進行聚合
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
searchSourceBuilder.aggregation(attr_agg);
log.debug("構建的DSL語句 {}",searchSourceBuilder.toString());
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);
return searchRequest;
測試,檢視聚合出來的品牌、分類、屬性資訊
6.10檢索服務-SearchRequest分析&封裝
主要步驟:
- 1.商品資訊
- 2.當前商品涉及到的所有屬性資訊
- 3.當前商品涉及到的所有品牌資訊
- 4.當前商品涉及到的所有分類資訊
- 5.分頁資訊
- 頁碼
- 總記錄數
- 總頁碼
程式碼
SearchResult result = new SearchResult();
//1、返回的所有查詢到的商品
SearchHits hits = response.getHits();
List<SkuEsModel> esModels = new ArrayList<>();
//遍歷所有商品資訊
if (hits.getHits() != null && hits.getHits().length > 0) {
for (SearchHit hit : hits.getHits()) {
String sourceAsString = hit.getSourceAsString();
SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
//判斷是否按關鍵字檢索,若是就顯示高亮,否則不顯示
if (!StringUtils.isEmpty(param.getKeyword())) {
//拿到高亮資訊顯示標題
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
String skuTitleValue = skuTitle.getFragments()[0].string();
esModel.setSkuTitle(skuTitleValue);
}
esModels.add(esModel);
}
}
result.setProduct(esModels);
//2、當前商品涉及到的所有屬性資訊
List<SearchResult.AttrVo> attrVos = new ArrayList<>();
//獲取屬性資訊的聚合
ParsedNested attrsAgg = response.getAggregations().get("attr_agg");
ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");
for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
//1、得到屬性的id
long attrId = bucket.getKeyAsNumber().longValue();
attrVo.setAttrId(attrId);
//2、得到屬性的名字
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
attrVo.setAttrName(attrName);
//3、得到屬性的所有值
ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());
attrVo.setAttrValue(attrValues);
attrVos.add(attrVo);
}
result.setAttrs(attrVos);
//3、當前商品涉及到的所有品牌資訊
List<SearchResult.BrandVo> brandVos = new ArrayList<>();
//獲取到品牌的聚合
ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
for (Terms.Bucket bucket : brandAgg.getBuckets()) {
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
//1、得到品牌的id
long brandId = bucket.getKeyAsNumber().longValue();
brandVo.setBrandId(brandId);
//2、得到品牌的名字
ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandName(brandName);
//3、得到品牌的圖片
ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandImg(brandImg);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
//4、當前商品涉及到的所有分類資訊
//獲取到分類的聚合
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
//得到分類id
String keyAsString = bucket.getKeyAsString();
catalogVo.setCatalogId(Long.parseLong(keyAsString));
//得到分類名
ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
catalogVo.setCatalogName(catalogName);
catalogVos.add(catalogVo);
}
result.setCatalogs(catalogVos);
//===============以上可以從聚合資訊中獲取====================//
//5、分頁資訊-頁碼
result.setPageNum(param.getPageNum());
//5、1分頁資訊、總記錄數
long total = hits.getTotalHits().value;
result.setTotal(total);
//5、2分頁資訊-總頁碼-計算
int totalPages = (int)total % EsConstant.PRODUCT_PAGESIZE == 0 ?
(int)total / EsConstant.PRODUCT_PAGESIZE : ((int)total / EsConstant.PRODUCT_PAGESIZE + 1);
result.setTotalPages(totalPages);
6.11檢索服務-驗證結果封裝正確性
檢視SearchResponse
返回結果
獲取高亮顯示的標題
if (!StringUtils.isEmpty(param.getKeyword())) {
//拿到高亮資訊顯示標題
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
String skuTitleValue = skuTitle.getFragments()[0].string();
esModel.setSkuTitle(skuTitleValue);
}
6.12檢索服務-頁面基本資料渲染
主要步驟:
- 商品列表:
rig_tab
- 品牌資訊:
JD_nav_wrap
- 分類資訊:
JD_pre
- 屬性資訊:
JD_pre
商品列表:rig_tab
<div class="rig_tab">
<div th:each="product : ${result.getProduct()}">
<div class="ico">
<i class="iconfont icon-weiguanzhu"></i>
<a href="/static/search/#">關注</a>
</div>
<p class="da">
<a th:href="|http://item.gulimall.com/${product.skuId}.html|" >
<img class="dim" th:src="${product.skuImg}">
</a>
</p>
<ul class="tab_im">
<li><a href="/static/search/#" title="黑色">
<img th:src="${product.skuImg}"></a></li>
</ul>
<p class="tab_R">
<span th:text="'¥' + ${product.skuPrice}">¥5199.00</span>
</p>
<p class="tab_JE">
<a href="/static/search/#" th:utext="${product.skuTitle}">
Apple iPhone 7 Plus (A1661) 32G 黑色 移動聯通電信4G手機
</a>
</p>
<p class="tab_PI">已有<span>11萬+</span>熱門評價
<a href="/static/search/#">二手有售</a>
</p>
<p class="tab_CP"><a href="/static/search/#" title="穀粒商城Apple產品專營店">穀粒商城Apple產品...</a>
<a href='#' title="聯絡供應商進行諮詢">
<img src="/static/search/img/xcxc.png">
</a>
</p>
<div class="tab_FO">
<div class="FO_one">
<p>自營
<span>穀粒商城自營,品質保證</span>
</p>
<p>滿贈
<span>該商品參加滿贈活動</span>
</p>
</div>
</div>
</div>
</div>
品牌資訊:JD_nav_wrap
<!--品牌-->
<div class="JD_nav_wrap">
<div class="sl_key">
<span><b>品牌:</b></span>
</div>
<div class="sl_value">
<div class="sl_value_logo">
<ul>
<li th:each="brand : ${result.brands}">
<a href="#"
th:href="${'javascript:searchProducts("brandId",'+brand.brandId+')'}">
<img th:src="${brand.brandImg}" alt="">
<div th:text="${brand.brandName}">
華為(HUAWEI)
</div>
</a>
</li>
</ul>
</div>
</div>
<div class="sl_ext">
<a href="/static/search/#">
更多
<i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="/static/search/#">
多選
<i>+</i>
<span>+</span>
</a>
</div>
</div>
分類資訊:JD_pre
<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="/static/search/#"
th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}"
th:text="${catalog.catalogName}">5.56英寸及以上
</a>
</li>
</ul>
</div>
<div class="sl_ext">
<a href="/static/search/#">
更多
<i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
<b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
</a>
<a href="/static/search/#">
多選
<i>+</i>
<span>+</span>
</a>
</div>
</div>
屬性資訊
<!--其它的所有需要展示的屬性-->
<div class="JD_pre" th:each="attr : ${result.attrs}">
<div class="sl_key">
<span th:text="${attr.attrName}">螢幕尺寸:</span>
</div>
<div class="sl_value">
<ul>
<li th:each="attrValue : ${attr.attrValue}">
<a href="/static/search/#"
th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+attrValue+'")'}"
th:text="${attrValue}">5.56英寸及以上
</a>
</li>
</ul>
</div>
</div>
預設查詢所有商品,不管有沒有庫存
6.13檢索服務-頁面篩選條件渲染
主要步驟:
-
條件拼接方法
-
品牌資訊條件拼接
-
分類資訊條件拼接
-
基礎屬性條件拼接
條件拼接方法,需要判斷一開始的查詢連結有沒有"?"
function searchProducts(name, value) {
//原來的頁面
var href = location.href + "";
if (href.indexOf("?") != -1) {
location.href = location.href + "&" + name + "=" + value;
} else {
location.href = location.href + "?" + name + "=" + value;
}
}
品牌資訊條件拼接,注意使用動態拼接後雙引號(''')使用"
轉義
<a href="#" th:href="${'javascript:searchProducts("brandId",'+brand.brandId+')'}">
<img th:src="${brand.brandImg}" alt="">
<div th:text="${brand.brandName}">
華為(HUAWEI)
</div>
</a>
分類資訊條件拼接,注意使用動態拼接後雙引號(''')使用"
轉義
<a href="/static/search/#" th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}"
th:text="${catalog.catalogName}">5.56英寸及以上
</a>
基礎屬性條件拼接,注意使用動態拼接後雙引號(''')使用"
轉義
<a href="/static/search/#"
th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+attrValue+'")'}"
th:text="${attrValue}">5.56英寸及以上
</a>
6.14檢索服務-頁面分頁資料渲染
主要步驟:
- 1.搜尋頁搜尋功能
- 2.分頁
th:attr="pn=${result.pageNum - 1}"
:自定義特性,代表當前頁數th:if="${result.pageNum>1}"
:當前頁數>1顯示上一頁style=${navs == result.pageNum?'border: 0;color:#ee2222;background: #fff':''}"
:選中時顯示樣式<a class="page_a" th:attr="pn=${navs}" th:each="navs : ${result.pageNavs}">\[\[${navs}\]\]</a>
:迴圈顯示頁數th:if="${result.pageNum<result.totalPages}"
:當前頁<總頁數顯示下一頁
搜尋頁搜尋功能
分頁樣式
<!--分頁-->
<div class="filter_page">
<div class="page_wrap">
<span class="page_span1">
<a class="page_a" th:attr="pn=${result.pageNum - 1}" href="/static/search/#"
th:if="${result.pageNum>1}">
< 上一頁
</a>
<a class="page_a"
th:attr="pn=${navs},style=${navs == result.pageNum?'border: 0;color:#ee2222;background: #fff':''}"
th:each="navs : ${result.pageNavs}">[[${navs}]]</a>
<a class="page_a" th:attr="pn=${result.pageNum + 1}"
th:if="${result.pageNum<result.totalPages}">
下一頁 >
</a>
</span>
<span class="page_span2">
<em>共<b>[[${result.totalPages}]]</b>頁 到第</em>
<input type="number" value="1">
<em>頁</em>
<a class="page_submit">確定</a>
</span>
</div>
</div>
分頁事件
$(".page_a").click(function () {
var pn = $(this).attr("pn");
var href = location.href;
if (href.indexOf("pageNum") != -1) {
//替換pageNum
location.href = replaceParamVal(href, "pageNum", pn,false);
} else {
location.href = location.href + "&pageNum=" + pn;
}
return false;
})
6.15檢索服務-頁面排序功能
主要步驟:
-
1.自定義特性
sort="hotScore"
sort="saleCount"
sort="skuPrice"
-
2.點選事件:
- 清除
sort_a
的所有樣式(選中高亮)和升序↑
降序↓
的標誌 - 切換
sort_a
的升序降序的樣式 - 清除之前的升序
↑
降序↓
的標誌,如果有desc
降序樣式就新增降序↓
的標誌,沒有desc
降序樣式就新增升序↑
的標誌,
- 清除
樣式
<div class="filter_top_left">
<a class="sort_a" sort="hotScore" href="/static/search/#">綜合排序</a>
<a class="sort_a" sort="saleCount" href="/static/search/#">銷量</a>
<a class="sort_a" sort="skuPrice" href="/static/search/#">價格</a>
</div>
點選事件
// 排序
$(".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);
// 禁用預設行為 防止a標籤跳轉
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.16檢索服務-頁面排序欄位回顯
主要步驟:
-
th:with="p = ${param.sort}"
:param.sort
獲取位址列中sort
的引數,不支援直接使用param
比較 -
th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}"
:如果param
不為空,並且sort
引數hotScore
開始,desc
結尾,class
為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;' }"
:預設是綜合排序,如果param
為空,並且sort
引數hotScore
開始,設定為選中高亮樣式 -
[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc')) ?'↑':'↓' }]]
:如果param
不為空,並且sort
引數hotScore
開始,desc
結尾,新增升序標誌↑
樣式
<!--綜合排序-->
<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 sort="hotScore" class="sort_a">評論分</a>
<a sort="hotScore" class="sort_a">上架時間</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="/static/search/#" class="prev"><</a>
<a href="/static/search/#" class="next"> > </a>
</div>
</div>
6.17檢索服務-頁面價格區間搜尋
價格區間
主要步驟:
-
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"
:param.skuPrice
獲取位址列skuPrice
引數 -
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"
:如果priceRange
不為空,按照_
擷取priceRange
,獲取priceRange
之前的數字,比如5000_8000,就是5000 -
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}"
:如果priceRange
不為空,按照_
擷取priceRange
,獲取priceRange
之後的數字,比如5000_8000,就是8000 -
查詢時拼接
skuPriceFrom
和skuPriceTo
文字框的引數新增到位址列
樣式
<input id="skuPriceFrom" type="number"
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"
style="width: 100px; margin-left: 30px">
-
<input id="skuPriceTo" type="number"
th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}"
style="width: 100px">
<button id="skuPriceSearchBtn">確定</button>
事件
// 價格區間
$("#skuPriceSearchBtn").click(function () {
let from = $(`#skuPriceFrom`).val();
let to = $(`#skuPriceTo`).val();
let query = from + "_" + to;
location.href = replaceParamVal(location.href, "skuPrice", query,false);
});
僅顯示有貨
主要步驟:
th:with="check = ${param.hasStock}"
:獲取位址列hasStock
引數th:checked="${#strings.equals(check,'1')?true:false}"
:如果hasStock
為1就選中- 選中和取消選中的時候位址列更新
hasStock
引數
樣式
<a th:with="check = ${param.hasStock}">
<input id="showHasStock" type="checkbox" th:checked="${#strings.equals(check,'1')?true:false}">
僅顯示有貨
</a>
事件
// 是否有庫存
$("#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;
});
keyword
function searchProducts(name, value) {
//原來的頁面
location.href = replaceParamVal(location.href,name,value,true)
}
6.18檢索服務-麵包屑導航
主要步驟:
- 1.獲取基礎屬性
attrs
(attrs=1_5寸:8寸
) - 2.遠端呼叫商品服務根據
attrId
查詢屬性名稱
gulimall-search
匯入遠端呼叫依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
遠端呼叫
獲取基礎屬性attrs
(attrs=1_5寸:8寸
)
6.19檢索服務-條件刪除與URL編碼問題
主要步驟:
- 修改編碼為
UTF-8
- 替換空格,將後端的空格符號
+
替換為%20
- 前端動態生成麵包屑導航
修改編碼為UTF-8
,將後端的空格符號+
替換為%20
前端動態生成麵包屑導航
<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> x</a>
</div>
6.20檢索服務-條件篩選聯動
主要步驟:
- 1.遠端呼叫
gulimall-product
查詢品牌服務 - 2.替換品牌連結地址
- 3.遠端呼叫
gulimall-product
查詢商品分類
if (param.getBrandId() != null && param.getBrandId().size() > 0) {
List<SearchResult.NavVo> navVos = result.getNavs();
SearchResult.NavVo navVo = new SearchResult.NavVo();
navVo.setNavName("品牌");
R r = productFeignService.brandsInfo(param.getBrandId());
if (r.getCode() == 0) {
List<BrandVo> brands = r.getData("brand", new TypeReference<List<BrandVo>>() {
});
String replace = "";
StringBuffer buffer = new StringBuffer();
for (BrandVo brandVo : brands) {
buffer.append(brandVo.getName() + ";");
replace = replaceQueryString(param, "brandId", brandVo.getBrandId().toString());
}
navVo.setNavValue(buffer.toString());// 品牌拼接值
navVo.setLink("http://search.gulimall.com/list.html?" + replace);// 回退品牌面包屑等於刪除所有品牌條件
navVos.add(navVo);
}
}
// 構建麵包屑導航資料_分類
if (param.getCatalog3Id() != null) {
List<SearchResult.NavVo> navs = result.getNavs();
SearchResult.NavVo nav = new SearchResult.NavVo();
nav.setNavName("分類");
R r = productFeignService.categoryinfo(param.getCatalog3Id());
if (r.getCode() == 0) {
CategoryVo categoryVo = r.getData(new TypeReference<CategoryVo>() {
});
nav.setNavValue(categoryVo.getName());// 分類名
}
// StringBuffer buffer = new StringBuffer();
// String replace = replaceQueryString(param, "catalog3Id", param.getCatalog3Id().toString());
// nav.setLink("http://search.gulimall.com/list.html?" + replace);
navs.add(nav);
}
7.商城業務-非同步
7.1非同步複習
主要步驟:
- 1.繼承
Thread
- 2.實現
Runnable
介面 - 3.實現
Callable
介面 - 4.執行緒池:執行緒池直接提交任務
區別:
- 1、2不能得到返回值
- 1、2、3都不能直接控制資源
- 4可以控制資源
public static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 1.繼承Thread
* 2.實現Runnable介面
* 3.實現Callable介面
* 4.執行緒池:執行緒池直接提交任務
*
* 區別:
* 1、2不能得到返回值
* 1、2、3都不能直接控制資源
* 4可以控制資源
* */
System.out.println("main......start.....");
Thread thread = new Thread01();
thread.start();
System.out.println("main......end.....");
System.out.println("main......start.....");
Runable01 runable01 = new Runable01();
new Thread(runable01).start();
System.out.println("main......start.....");
System.out.println("main......start.....");
FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
new Thread(futureTask).start();
System.out.println(futureTask.get());
System.out.println("main......end.....");
System.out.println("main......start.....");
service.execute(new Runable01());
Future<Integer> submit = service.submit(new Callable01());
submit.get();
System.out.println("main......end.....");
}
private static void threadPool() {
ExecutorService threadPool = new ThreadPoolExecutor(
200,
10,
10L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(10000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//定時任務的執行緒池
ExecutorService service = Executors.newScheduledThreadPool(2);
}
public static class Thread01 extends Thread {
@Override
public void run() {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("執行結果:" + i);
}
}
public static class Runable01 implements Runnable {
@Override
public void run() {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("執行結果:" + i);
}
}
public static class Callable01 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("執行結果:" + i);
return i;
}
}
7.2執行緒池詳解
執行緒池的七大引數:
corePoolSize
:池中一直保持的執行緒的數量,即使執行緒空閒。除非設定allowCoreThreadTimeOut
maximumPoolSize
:池中允許的最大的執行緒數keepAliveTime
:當執行緒數大於核心執行緒數的時候,執行緒在最大多長時間沒有接到新任務就會終止釋放, 最終執行緒池維持在corePoolSize
大小unit
:時間單位workQueue
:阻塞佇列,用來儲存等待執行的任務,如果當前對執行緒的需求超過了corePoolSize
大小,就會放在這裡等待空閒執行緒執行。threadFactory
:建立執行緒的工廠,比如指定執行緒名等handler
:拒絕策略,如果執行緒滿了,執行緒池就會使用拒絕策略。
一個執行緒池 core 7;max 20;queue:50;100併發進來怎麼分配ide
7個立即執行,50個放入佇列,在執行13個(max-core)
,剩下30個使用拒絕策略
常見的 4 種執行緒池
newCachedThreadPool
:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。newFixedThreadPool
:建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。newScheduledThreadPool
:建立一個定長執行緒池,支援定時及週期性任務執行。newSingleThreadExecutor
:建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// Executors.newCachedThreadPool() core是0,所有都可回收()
// Executors.newFixedThreadPao() 固定大小,core=max;都不可回收
// Executors.newScheduledThreudPool() 定時任務的執行緒池
// Executors.newsingleThreadExecutor() 單執行緒的執行緒池,後臺從佇列裡面獲取任務,挨個執行
7.3CompletableFuture
為什麼使用執行緒池
- 降低資源的消耗
- 透過重複利用已經建立好的執行緒降低執行緒的建立和銷燬帶來的損耗
- 提高響應速度
- 因為執行緒池中的執行緒數沒有超過執行緒池的最大上限時,有的執行緒處於等待分配任務 的狀態,當任務來時無需建立新的執行緒就能執行
- 提高執行緒的可管理性
- 執行緒池會根據當前系統特點對池內的執行緒進行最佳化處理,減少建立和銷燬執行緒帶來 的系統開銷。無限的建立和銷燬執行緒不僅消耗系統資源,還降低系統的穩定性,使用執行緒池進行統一分配
CompletableFuture 非同步編排
業務場景
在 Java 8 中, 新增加了一個包含 50 個方法左右的類: CompletableFuture,提供了非常強大的 Future 的擴充套件功能,可以幫助我們簡化非同步程式設計的複雜性,提供了函數語言程式設計的能力,可以 透過回撥的方式處理計算結果,並且提供了轉換和組合 CompletableFuture 的方法。 CompletableFuture 類實現了 Future 介面,所以你還是可以像以前一樣透過get
方法阻塞或 者輪詢的方式獲得結果,但是這種方式不推薦使用。 CompletableFuture 和 FutureTask 同屬於 Future 介面的實現類,都可以獲取執行緒的執行結果
7.4CompletableFuture-啟動非同步任務
CompletableFuture
-
runxxx 都是沒有返回結果的
-
supplyxxx 都是可以獲取返回結果的
-
可以傳入自定義的執行緒池,否則就用預設的執行緒池
public static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main......start.....");
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("執行結果:" + i);
}, executor);
CompletableFuture<Integer> CFres = CompletableFuture.supplyAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("執行結果:" + i);
return i;
}, executor);
Integer res = CFres.get();
System.out.println(res);
System.out.println("main......end.....");
}
7.5CompletableFuture-完成回撥與非同步感知
計算完成時回撥方法
whenComplete 可以處理正常和異常的計算結果,exceptionally 處理異常情況
whenComplete 和 whenCompleteAsync 的區別:
- whenComplete:是執行當前任務的執行緒執行繼續執行 whenComplete 的任務。
- whenCompleteAsync:是執行把 whenCompleteAsync 這個任務繼續提交給執行緒池
System.out.println("main......start.....");
CompletableFuture<Integer> CFres = CompletableFuture.supplyAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("執行結果:" + i);
return i;
}, executor).whenComplete((res,ex)->{
System.out.println("非同步任務執行完成...結果是:"+res+";異常是:"+ex);
}).exceptionally(throwable -> {
// 可以感知異常 同時返回預設值
return 10;
});
Integer res = CFres.get();
System.out.println(res);
System.out.println("main......end.....");
7.6CompletableFuture-handle最終處理
handle
和 complete 一樣,可對結果做最後的處理(可處理異常),可改變返回值。
// handle
System.out.println("main......start.....");
CompletableFuture<Integer> CFres = CompletableFuture.supplyAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 2;
// int i = 10 / 0; //exception
System.out.println("執行結果:" + i);
return i;
}, executor).handle((res, ex) -> {
if (res != null) {
return res * 2;
}
if (ex != null) {
return 0;
}
return 0;
});
Integer res = CFres.get();
System.out.println(res);
System.out.println("main......end.....");
7.7CompletableFuture-執行緒序列化
thenRun、thenAccept、thenApply
- thenRun 方法:只要上面的任務執行完成,就開始執行 thenRun,只是處理完任務後,執行 thenRun 的後續操作
- thenAccept 方法:消費處理結果。接收任務的處理結果,並消費處理,無返回結果。
- thenApply 方法:當一個執行緒依賴另一個執行緒時,獲取上一個任務返回的結果,並返回當前 任務的返回值。
- 帶有 Async 預設是非同步執行的。
- 同之前。 以上都要前置任務成功完成
// 執行緒序列化
System.out.println("main......start.....");
CompletableFuture.supplyAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 5;
// int i = 10 / 0; //exception
System.out.println("執行結果:" + i);
return i;
}, executor).thenRunAsync(() -> {
System.out.println("任務執行完成" + Thread.currentThread().getId());
}, executor);
CompletableFuture.supplyAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 5;
// int i = 10 / 0; //exception
System.out.println("執行結果:" + i);
return i;
}, executor).thenAcceptAsync(res -> {
System.out.println("任務執行完成" + Thread.currentThread().getId());
System.out.println("上一步結果:"+res);
}, executor);
// supplyAsync:接受上一步結果,有返回值
CompletableFuture<String> CFres = CompletableFuture.supplyAsync(() -> {
System.out.println("當前執行緒:" + Thread.currentThread().getId());
int i = 10 / 5;
// int i = 10 / 0; //exception
System.out.println("執行結果:" + i);
return i;
}, executor).thenApplyAsync(res -> {
return "hello" + res;
}, executor);
String res = CFres.get();
System.out.println(res);
System.out.println("main......end.....");
7.8CompletableFuture-倆任務組合-都要完成
倆任務組合-都要完成
- runAfterBoth:組合兩個 future,不需要獲取 future 的結果,只需兩個 future 處理完任務後, 處理該任務。
- thenAcceptBoth:組合兩個 future,獲取兩個 future 任務的返回結果,然後處理任務,沒有 返回值。
- thenCombine:組合兩個 future,獲取兩個 future 的返回結果,並返回當前任務的返回值
建立2個任務
// 組合任務 都要完成
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
System.out.printf("任務1執行緒:" + Thread.currentThread().getId());
System.out.println("任務1結束");
return "hello2";
}, executor);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.printf("任務2執行緒:" + Thread.currentThread().getId());
System.out.println("任務2結束");
return "hello1";
}, executor);
runAfterBothAsync
future1.runAfterBothAsync(future2,()->{
System.out.printf("任務1,任務2完成");
},executor);
thenAcceptBothAsync
future1.thenAcceptBothAsync(future2,(f1,f2)->{
System.out.println("任務1結果:"+f1);
System.out.println("任務2結果:"+f2);
System.out.println("任務結束");
},executor);
CompletableFuture
CompletableFuture<String> future3 = future1.thenCombineAsync(future2, (f1, f2) -> {
System.out.println("任務1結果:" + f1);
System.out.println("任務2結果:" + f2);
return f1 + " " + f2 + " " + "hello3";
}, executor);
System.out.printf("任務3結果:" + future3.get());
7.9CompletableFuture-倆任務組合-一個完成
倆任務組合-一個完成
- runAfterEither:兩個任務有一個執行完成,不需要獲取 future 的結果,處理任務,也沒有返 回值。
- acceptEither:兩個任務有一個執行完成,獲取它的返回值,處理任務,沒有新的返回值。
- applyToEither:兩個任務有一個執行完成,獲取它的返回值,處理任務並有新的返回值。
建立2個任務
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務1執行緒:" + Thread.currentThread().getId());
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
System.out.println("任務1結束");
return "hello1";
}, executor);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務2執行緒:" + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("任務2結束");
return "hello2";
}, executor);
runAfterEitherAsync
future1.runAfterEitherAsync(future2,()->{
System.out.println("任務3執行緒:" + Thread.currentThread().getId());
System.out.println("任務3結束");
});
acceptEitherAsync
future1.acceptEitherAsync(future2, (res) -> {
System.out.println("接受結果:" + res);
System.out.println("任務3執行緒:" + Thread.currentThread().getId());
System.out.println("任務3結束");
});
applyToEitherAsync
CompletableFuture<String> future3 = future1.applyToEitherAsync(future2, (res) -> {
System.out.println("接受結果:" + res);
System.out.println("任務3執行緒:" + Thread.currentThread().getId());
System.out.println("任務3結束");
return res + " hello3";
});
System.out.println("任務3結果:" + future3.get());
7.10CompletableFuture-多工組合
多工組合
allOf
:等待所有任務完成anyOf
:只要有一個任務完成
建立3個任務,任務2休眠3s,模擬耗時操作
System.out.println("main......start.....");
// 多工組合
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
System.out.println("獲取圖片");
return "hello1.jpg";
}, executor);
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
System.out.println("獲取屬性");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "白色12+512GB";
}, executor);
CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
System.out.println("獲取描述");
return "華為Mate70";
}, executor);
allOf
CompletableFuture<Void> allof = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);
allof.get(); //阻塞等待三個任務完成
// // allof.join();
System.out.println(futureImg.get());
System.out.println(futureAttr.get());
System.out.println(futureDesc.get());
anyOf
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
anyOf.get();
System.out.println(anyOf.get());
System.out.println("main......end.....");
8.商城業務-商品詳情
8.1環境搭建
主要步驟:
- 1.配置
Hosts
- 2.配置
nginx
- 3.上傳商品服務的靜態資源到
nginx
的static/item
目錄下 - 4.配置
item.gulimall.com
的閘道器 - 5.
gulimall-product
新增控制器跳轉和商品靜態頁面 - 6.
gulimall-search
修改商品詳情跳轉地址
管理員執行SwitchHosts
192.168.188.180 item.gulimall.com
因為*.gulimall.com
可以匹配item.gulimall.com
,這裡不需要配置,檢查一下即可
上傳商品服務的靜態資源到nginx
的static/item
目錄下
配置item.gulimall.com
的閘道器,讓item.gulimall.com
可以轉發到商品服務
- id: gulimall_host_route
uri: lb://gulimall-product
predicates:
- Host=gulimall.com,item.gulimall.com
gulimall-product
新增商品靜態頁面,並改名item.html
替換靜態資源地址為nginx
地址
href="
href="/static/item/
替換圖片地址為nginx
地址
src="
src="/static/item/
gulimall-product
新增控制器跳轉
@Controller
public class ItemController {
/**
* 展示當前sku的詳情
* @param skuId
* @return
*/
@GetMapping("/{skuId}.html")
public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException {
System.out.println("準備查詢" + skuId + "詳情");
return "item";
}
}
gulimall-search
修改商品詳情的連結地址
<p class="da">
<a th:href="|http://item.gulimall.com/${product.skuId}.html|">
<img class="dim" th:src="${product.skuImg}">
</a>
</p>
訪問http://item.gulimall.com/1.html
8.2模型抽取
主要步驟:
- 1.
sku
基本資訊獲取pms_sku_info
- 2.
sku
的圖片資訊pms_sku_images
- 3.獲取
spu
的銷售屬性組合 - 4.獲取
spu
的介紹 - 5.獲取
spu
的規格引數資訊。
@Data
public class SkuItemVo {
//1、sku基本資訊的獲取 pms_sku_info
private SkuInfoEntity info;
private boolean hasStock = true;
//2、sku的圖片資訊 pms_sku_images
private List<SkuImagesEntity> images;
//3、獲取spu的銷售屬性組合
private List<SkuItemSaleAttrVo> saleAttr;
//4、獲取spu的介紹
private SpuInfoDescEntity desc;
//5、獲取spu的規格引數資訊
private List<SpuItemAttrGroupVo> groupAttrs;
}
8.3規格引數
主要步驟:
- 1.sku基本資訊的獲取
- 2.sku的圖片資訊
- 4.獲取spu的介紹
- 5.獲取spu的規格引數資訊
1、2、3簡單的查詢
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
// 1、sku基本資訊的獲取 pms_sku_info
SkuInfoEntity info = this.getById(skuId);
skuItemVo.setInfo(info);
// 2、sku的圖片資訊 pms_sku_images
List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(imagesEntities);
// 3、獲取spu的銷售屬性組合
// 4、獲取spu的介紹
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(info.getSpuId());
skuItemVo.setDesc(spuInfoDescEntity);
// 5、獲取spu的規格引數資訊
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(info.getSpuId(), info.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
return skuItemVo;
}
獲取spu的規格引數資訊
AttrGroupServiceImpl
@Override
public List<SpuItemAttrGroupVo> getAttrGroupWithAttrsBySpuId(Long spuId, Long catalogId) {
//1、查出當前spu對應的所有屬性的分組資訊以及當前分組下的所有屬性對應的值
AttrGroupDao baseMapper = this.getBaseMapper();
List<SpuItemAttrGroupVo> vos = baseMapper.getAttrGroupWithAttrsBySpuId(spuId,catalogId);
return vos;
}
AttrGroupDao.xml
<resultMap id="spuAttrGroup" type="com.peng.product.vo.SpuItemAttrGroupVo">
<result property="groupName" column="attr_group_name"/>
<collection property="attrs" ofType="com.peng.product.vo.Attr">
<result property="attrId" column="attr_id"></result>
<result property="attrName" column="attr_name"></result>
<result property="attrValue" column="attr_value"></result>
</collection>
</resultMap>
<select id="getAttrGroupWithAttrsBySpuId" resultMap="spuAttrGroup">
SELECT
product.spu_id,
pag.attr_group_id,
pag.attr_group_name,
product.attr_id,
product.attr_name,
product.attr_value
FROM
pms_product_attr_value product
LEFT JOIN pms_attr_attrgroup_relation paar ON product.attr_id = paar.attr_id
LEFT JOIN pms_attr_group pag ON paar.attr_group_id = pag.attr_group_id
WHERE
product.spu_id = #{spuId}
AND pag.catelog_id = #{catalogId}
</select>
單元測試
@Test
public void test(){
List<SpuItemAttrGroupVo> attrGroupWithAttrsBySpuId = attrGroupService.getAttrGroupWithAttrsBySpuId(1L, 225L);
System.out.println(attrGroupWithAttrsBySpuId);
}
8.4銷售屬性組合
主要步驟:
- 查詢sku對應的銷售屬性
- 單元測試
SkuSaleAttrValueDao
List<SkuItemSaleAttrVo> getSaleAttrBySpuId(@Param("spuId")Long spuId);
SkuSaleAttrValueDao.xml
<select id="getSaleAttrBySpuId" resultMap="skuItemSaleAttrVo">
SELECT
ssav.attr_id attr_id,
ssav.attr_name attr_name,
ssav.attr_value,
group_concat( DISTINCT info.sku_id ) sku_ids
FROM
pms_sku_info info
LEFT JOIN pms_sku_sale_attr_value ssav ON ssav.sku_id = info.sku_id
WHERE
info.spu_id = #{spuId}
GROUP BY
ssav.attr_id,
ssav.attr_name,
ssav.attr_value
</select>
單元測試
@Test
public void test2(){
List<SkuItemSaleAttrVo> saleAttrBySpuId = skuSaleAttrValueService.getSaleAttrBySpuId(1L);
System.out.println(saleAttrBySpuId);
}
8.5詳情頁渲染
主要步驟:
- 預設圖片
- 商品標題
- 有/無貨(庫存)狀態
- 銷售屬性
- 商品介紹
- 規格與包裝
預設圖片
<div class="imgbox">
<div class="probox">
<img class="img1" alt="" th:src="${item.info.skuDefaultImg}">
<div class="hoverbox"></div>
</div>
<div class="showbox">
<img class="img1" alt="" th:src="${item.info.skuDefaultImg}">
</div>
</div>
<div class="box-lh">
<div class="box-lh-one">
<ul>
<li th:each="img : ${item.images}"><img th:src="${img.imgUrl}"/></li>
</ul>
</div>
<div id="left">
<
</div>
<div id="right">
>
</div>
</div>
<div class="boxx-one">
<ul>
<li>
<span>
<img src="/static/item/img/b769782fe4ecca40913ad375a71cb92d.png" alt="" />關注
</span>
<span>
<img src="/static/item/img/9224fcea62bfff479a6712ba3a6b47cc.png" alt="" />
對比
</span>
</li>
<li>
</li>
</ul>
</div>
商品標題
<div class="box-name" th:text="${item.info.skuTitle}">
華為 HUAWEI Mate 10 6GB+128GB 亮黑色 移動聯通電信4G手機 雙卡雙待
</div>
<div class="box-hide" th:text="${item.info.skuSubtitle}">預訂使用者預計11月30日左右陸續發貨!麒麟970晶片!AI智慧拍照!
<a href="/static/item/"><u></u></a>
</div>
無貨
<li>
<span th:text="${item.hasStock?'有貨':'無貨'}">無貨</span>, 此商品暫時售完
</li>
銷售屬性
<div class="box-attr-3">
<div class="box-attr clear" th:each="attr : ${item.saleAttr}">
<dl>
<dt>選擇[[${attr.attrName}]]</dt>
<dd th:each="val : ${attr.attrValues}">
<a th:attr=" class=${#lists.contains(#strings.listSplit(val.skuIds,','),item.info.skuId.toString())
? 'sku_attr_value checked': 'sku_attr_value'}, skus=${val.skuIds} "
>
[[${val.attrValue}]]
<!-- <img src="/static/item/img/59ddfcb1Nc3edb8f1.jpg" /> -->
</a>
</dd>
</dl>
</div>
</div>
商品介紹
<div class="shanpinsssss">
<img class="xiaoguo" th:src="${descp}"
th:each="descp : ${#strings.listSplit(item.desc.decript,',')}"/>
</div>
規格與包裝
<li class="baozhuang actives" id="li2">
<div class="guiGebox">
<div class="guiGe" th:each="group : ${item.groupAttrs}">
<h3 th:text="${group.groupName}">主體</h3>
<dl>
<div th:each="attr : ${group.attrs}">
<dt th:text="${attr.attrName}">品牌</dt>
<dd th:text="${attr.attrValue}">華為(HUAWEI)</dd>
</div>
</dl>
</div>
<div class="package-list">
<h3>包裝清單</h3>
<p>手機(含內建電池) X 1、5A大電流華為SuperCharge充電器X 1、5A USB資料線 X 1、半入耳式線控耳機 X 1、快速指南X 1、三包憑證 X
1、取卡針 X 1、保護殼 X 1</p>
</div>
</div>
</li>
8.6銷售屬性渲染
主要步驟:
- 銷售屬性渲染的時候如果當前
skuId
包含銷售屬性就新增選中的class
- 頁面重新整理時給屬性選中的class加樣式
銷售屬性渲染的時候如果當前skuId
包含銷售屬性就新增選中的class
<dl>
<dt>選擇[[${attr.attrName}]]</dt>
<dd th:each="val : ${attr.attrValues}">
<a th:attr=" class=${#lists.contains(#strings.listSplit(val.skuIds,','),item.info.skuId.toString())
? 'sku_attr_value checked': 'sku_attr_value'}, skus=${val.skuIds} "
>
[[${val.attrValue}]]
<!-- <img src="/static/item/img/59ddfcb1Nc3edb8f1.jpg" /> -->
</a>
</dd>
</dl>
頁面重新整理時給屬性選中的class加樣式
$(function () {
changeCheckedStyle();
});
function changeCheckedStyle() {
$(".sku_attr_value").parent().css({"border": "solid 1px #ccc"});
$("a[class='sku_attr_value checked']").parent().css({"border": "solid 1px red"});
};
8.7sku組合切換
主要步驟:
-
點選屬性時清空當前銷售屬性組的所有選中樣式(
checked
),給當前點選元素新增checked
類名 -
獲取所有選中的銷售屬性
skus
,skus
是擁有該銷售屬性的所有商品skuId
集合 -
根據
skus
求交集,得到skuId
-
跳轉到對應的商品詳情頁面
程式碼
$(".sku_attr_value").click(function () {
// 1、點選的元素新增上自定義的屬性
let skus = new Array();
let curr = $(this).attr("skus").split(",");
$(this).parent().parent().find(".sku_attr_value").removeClass("checked");
$(this).addClass("checked");
changeCheckedStyle();
$("a[class='sku_attr_value checked']").each(function () {
skus.push($(this).attr("skus").split(","));
});
let filterEle = skus[0];
for (let i = 1; i < skus.length; i++) {
filterEle = $(filterEle).filter(skus[i])[0];
}
location.href = "http://item.gulimall.com/" + filterEle + ".html";
return false;
});
8.8非同步編排最佳化
主要步驟:
-
匯入自定義配置依賴
configuration-processor
-
gulimall-product
增加執行緒池配置 -
使用非同步編排最佳化商品詳情查詢
supplyAsync
:用於非同步執行一個返回結果的任務runAsync
:用於非同步執行一個不返回結果的任務。thenAcceptAsync
:用於在CompletableFuture
完成後非同步執行一個操作,該操作使用CompletableFuture
的結果但不返回新結果。它接受一個Consumer
介面實現,並返回一個新的CompletableFuture
,該物件在操作完成時表示完成狀態。
建立配置檔案ThreadPoolConfigProperties
,並在application.yaml
配置執行緒池引數
匯入自定義配置依賴configuration-processor
<!--configuration-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
ThreadPoolConfigProperties
@ConfigurationProperties(prefix = "gulimall.thread")
// @Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
application.yaml
gulimall:
thread:
coreSize: 20
maxSize: 200
keepAliveTime: 10
執行緒池配置
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
使用非同步編排最佳化商品詳情查詢
@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {
SkuItemVo skuItemVo = new SkuItemVo();
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
// 1、sku基本資訊的獲取 pms_sku_info
SkuInfoEntity info = this.getById(skuId);
skuItemVo.setInfo(info);
return info;
}, executor);
CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
// 3、獲取spu的銷售屬性組合
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((res) -> {
// 4、獲取spu的介紹 pms_spu_info_desc
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesc(spuInfoDescEntity);
}, executor);
CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync((res) -> {
// 5、獲取spu的規格引數資訊
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
}, executor);
// 2、sku的圖片資訊 pms_sku_images
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(imagesEntities);
}, executor);
// 等到所有任務都完成
CompletableFuture.allOf(infoFuture, saleAttrFuture, descFuture, baseAttrFuture, imageFuture).get();
return skuItemVo;
}