目录
- 首页检索功能
- 异步
- 商品详情
一、首页检索功能
1.环境准备
配置本地域名解析
配置nginx将请求转给网关
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 配置上游服务器(网关)
upstream gulishop{
server 192.168.0.100:88;
}
server {
listen 80;
server_name *.gulishop.cn gulishop.cn;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
# root html;
# index index.html index.htm;
proxy_set_header Host $host;
// 监听80的/路径下所有请求都转给网关
proxy_pass http://gulishop;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
配置网关转发规则,实现首页和搜索页之间的跳转
# 4. 首页
- id: guli-shop_host_route
uri: lb://guli-shop-product
predicates:
- Host=gulishop.cn
# 5. 首页搜索
- id: search_router
uri: lb://guli-shop-search
predicates:
- Host=search.gulishop.cn
编search微服务的controller处理检索请求
package henu.soft.xiaosi.search.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SearchController {
@GetMapping("/list.html")
public String listPage(){
// 处理检索属性
return "list";
}
}
2.确认检索属性、返回结果属性,编写vo实体
1.检索条件分析
- 全文检索:skuTitle —> keyword
- 排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
- 过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs
- 聚合:attrs
2.完整查询参数
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_高清屏
3.实体Vo
@Data
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;
}
检索结果
@Data
public class SearchResultVo {
/**
* 查询到的所有商品信息
*/
private List<SkuEsModel> products;
/**
* 当前页码
*/
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
@AllArgsConstructor
public static class BrandVo {
private Long brandId;
private String brandName;
private String brandImg;
}
@Data
@AllArgsConstructor
public static class AttrVo {
private Long attrId;
private String attrName;
private List<String> attrValue;
}
@Data
@AllArgsConstructor
public static class CatalogVo {
private Long catalogId;
private String catalogName;
}
}
4.编写DSL
# 重新设置mapping
PUT gulishop_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"
}
}
}
}
}
}
GET gulishop_product/_search
# 迁移数据
POST _reindex
{
"source": {
"index": "product"
},
"dest": {
"index":"gulishop_product"
}
}
# 查询
GET gulishop_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"2"
]
}
},
{
"term": {
"hasStock": "false"
}
},
{
"range": {
"skuPrice": {
"gte": 1000,
"lte": 7000
}
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "6"
}
}
}
]
}
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 5,
"highlight": {
"fields": {"skuTitle": {}},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
},
"aggs": {
"brandAgg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brandNameAgg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brandImgAgg": {
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"catalogAgg":{
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalogNameAgg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attrs":{
"nested": {
"path": "attrs"
},
"aggs": {
"attrIdAgg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},
"aggs": {
"attrNameAgg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
}
}
}
}
}
}
}
5.编写代码
逻辑
- 准备检索请求,调用封装方法
- 执行检索请求
- 分析响应数据,封装换成需要的格式
@Qualifier("client")
@Autowired
RestHighLevelClient restHighLevelClient;
/**
* 根据条件检索对应商品信息
*/
@Override
public SearchResultVo searchProductByParam(SearchParamVo searchParamVo) {
// 需要的返回数据
SearchResultVo resultVo = null;
// 准备检索请求,调用封装方法
SearchRequest searchRequest = helperBuildSearchRequest(searchParamVo);
try {
// 执行检索请求
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 分析响应数据,封装换成需要的格式
resultVo = helperBuilderResult(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
return resultVo;
}
获取searchRequest查询条件
/**
* 1. 封装方法,获取searchRequest
*
* @return
*/
private SearchRequest helperBuildSearchRequest(SearchParamVo searchParamVo) {
// 构建DSL语句
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/**
* 查询:模糊匹配、过滤(按照属性、分类、品牌、价格区间、库存)
*/
/**
* {
* query :{
* bool: {
* must:{},
* filter{}
* }
* },
* sort:[],
* from:0,
* size:1,
* highlight:{},
* aggs:{}
* }
*/
//1. 构建bool query
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//1.1 bool must
if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParamVo.getKeyword()));
}
//1.2 bool filter
//1.2.1 catalog
if (searchParamVo.getCatalog3Id() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParamVo.getCatalog3Id()));
}
//1.2.2 brand
if (searchParamVo.getBrandId() != null && searchParamVo.getBrandId().size() > 0) {
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", searchParamVo.getBrandId()));
}
//1.2.3 hasStock
if (searchParamVo.getHasStock() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParamVo.getHasStock() == 1));
}
//1.2.4 priceRange
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
if (!StringUtils.isEmpty(searchParamVo.getSkuPrice())) {
String[] prices = searchParamVo.getSkuPrice().split("_");
if (prices.length == 1) {
if (searchParamVo.getSkuPrice().startsWith("_")) {
rangeQueryBuilder.lte(Integer.parseInt(prices[0]));
} else {
rangeQueryBuilder.gte(Integer.parseInt(prices[0]));
}
} else if (prices.length == 2) {
//_6000会截取成["","6000"]
if (!prices[0].isEmpty()) {
rangeQueryBuilder.gte(Integer.parseInt(prices[0]));
}
rangeQueryBuilder.lte(Integer.parseInt(prices[1]));
}
boolQueryBuilder.filter(rangeQueryBuilder);
}
//1.2.5 attrs-nested
//attrs=1_5寸:8寸&2_16G:8G
List<String> attrs = searchParamVo.getAttrs();
BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
if (attrs != null && attrs.size() > 0) {
attrs.forEach(attr -> {
String[] attrSplit = attr.split("_");
queryBuilder.must(QueryBuilders.termQuery("attrs.attrId", attrSplit[0]));
String[] attrValues = attrSplit[1].split(":");
queryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
});
}
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", queryBuilder, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
//1. bool query构建完成
searchSourceBuilder.query(boolQueryBuilder);
//2. sort eg:sort=saleCount_desc/asc
if (!StringUtils.isEmpty(searchParamVo.getSort())) {
String[] sortSplit = searchParamVo.getSort().split("_");
searchSourceBuilder.sort(sortSplit[0], sortSplit[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
}
//3. 分页
searchSourceBuilder.from((searchParamVo.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
//4. 高亮highlight
if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
searchSourceBuilder.highlighter(highlightBuilder);
}
//5. 聚合
//5.1 按照brand聚合
TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId");
TermsAggregationBuilder brandNameAgg = AggregationBuilders.terms("brandNameAgg").field("brandName");
TermsAggregationBuilder brandImgAgg = AggregationBuilders.terms("brandImgAgg").field("brandImg");
brandAgg.subAggregation(brandNameAgg);
brandAgg.subAggregation(brandImgAgg);
searchSourceBuilder.aggregation(brandAgg);
//5.2 按照catalog聚合
TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId");
TermsAggregationBuilder catalogNameAgg = AggregationBuilders.terms("catalogNameAgg").field("catalogName");
catalogAgg.subAggregation(catalogNameAgg);
searchSourceBuilder.aggregation(catalogAgg);
//5.3 按照attrs聚合
NestedAggregationBuilder nestedAggregationBuilder = new NestedAggregationBuilder("attrs", "attrs");
//按照attrId聚合
TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
//按照attrId聚合之后再按照attrName和attrValue聚合
TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg").field("attrs.attrName");
TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue");
attrIdAgg.subAggregation(attrNameAgg);
attrIdAgg.subAggregation(attrValueAgg);
nestedAggregationBuilder.subAggregation(attrIdAgg);
searchSourceBuilder.aggregation(nestedAggregationBuilder);
System.out.println(searchSourceBuilder.toString());
SearchRequest request = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
return request;
}
封装查询结果 SearchResultVo 返回给前端页面
/**
* 2. 封装方法,将查询结果封装到resultVo
*
* @param
* @return
*/
private SearchResultVo helperBuilderResult(SearchResponse searchResponse, SearchParamVo searchParamVo) {
SearchResultVo result = new SearchResultVo();
SearchHits hits = searchResponse.getHits();
//1. 封装查询到的商品信息
if (hits.getHits() != null && hits.getHits().length > 0) {
List<SkuEsModel> skuEsModels = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
//设置高亮属性
if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
String highLight = skuTitle.getFragments()[0].string();
skuEsModel.setSkuTitle(highLight);
}
skuEsModels.add(skuEsModel);
}
result.setProducts(skuEsModels);
}
//2. 封装分页信息
//2.1 当前页码
result.setPageNum(searchParamVo.getPageNum());
//2.2 总记录数
long total = hits.getTotalHits().value;
result.setTotal(total);
//2.3 总页码
Integer totalPages = (int) total % EsConstant.PRODUCT_PAGESIZE == 0 ?
(int) total / EsConstant.PRODUCT_PAGESIZE : (int) total / EsConstant.PRODUCT_PAGESIZE + 1;
result.setTotalPages(totalPages);
List<Integer> pageNavs = new ArrayList<>();
for (int i = 1; i <= totalPages; i++) {
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
//3. 查询结果涉及到的品牌
List<SearchResultVo.BrandVo> brandVos = new ArrayList<>();
Aggregations aggregations = searchResponse.getAggregations();
//ParsedLongTerms用于接收terms聚合的结果,并且可以把key转化为Long类型的数据
ParsedLongTerms brandAgg = aggregations.get("brandAgg");
for (Terms.Bucket bucket : brandAgg.getBuckets()) {
//3.1 得到品牌id
Long brandId = bucket.getKeyAsNumber().longValue();
Aggregations subBrandAggs = bucket.getAggregations();
//3.2 得到品牌图片
ParsedStringTerms brandImgAgg = subBrandAggs.get("brandImgAgg");
String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
//3.3 得到品牌名字
Terms brandNameAgg = subBrandAggs.get("brandNameAgg");
String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
SearchResultVo.BrandVo brandVo = new SearchResultVo.BrandVo(brandId, brandName, brandImg);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
//4. 查询涉及到的所有分类
List<SearchResultVo.CatalogVo> catalogVos = new ArrayList<>();
ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
//4.1 获取分类id
Long catalogId = bucket.getKeyAsNumber().longValue();
Aggregations subcatalogAggs = bucket.getAggregations();
//4.2 获取分类名
ParsedStringTerms catalogNameAgg = subcatalogAggs.get("catalogNameAgg");
String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
SearchResultVo.CatalogVo catalogVo = new SearchResultVo.CatalogVo(catalogId, catalogName);
catalogVos.add(catalogVo);
}
result.setCatalogs(catalogVos);
//5 查询涉及到的所有属性
List<SearchResultVo.AttrVo> attrVos = new ArrayList<>();
//ParsedNested用于接收内置属性的聚合
ParsedNested parsedNested = aggregations.get("attrs");
ParsedLongTerms attrIdAgg = parsedNested.getAggregations().get("attrIdAgg");
for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
//5.1 查询属性id
Long attrId = bucket.getKeyAsNumber().longValue();
Aggregations subAttrAgg = bucket.getAggregations();
//5.2 查询属性名
ParsedStringTerms attrNameAgg = subAttrAgg.get("attrNameAgg");
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
//5.3 查询属性值
ParsedStringTerms attrValueAgg = subAttrAgg.get("attrValueAgg");
List<String> attrValues = new ArrayList<>();
for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
String attrValue = attrValueAggBucket.getKeyAsString();
attrValues.add(attrValue);
List<SearchResultVo.NavVo> navVos = new ArrayList<>();
}
SearchResultVo.AttrVo attrVo = new SearchResultVo.AttrVo(attrId, attrName, attrValues);
attrVos.add(attrVo);
}
result.setAttrs(attrVos);
// 6. 构建面包屑导航
List<String> attrs = searchParamVo.getAttrs();
if (attrs != null && attrs.size() > 0) {
List<SearchResultVo.NavVo> navVos = attrs.stream().map(attr -> {
String[] split = attr.split("_");
SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();
//6.1 设置属性值
navVo.setNavValue(split[1]);
//6.2 查询并设置属性名
try {
R r = productFeignService.info(Long.parseLong(split[0]));
if (r.getCode() == 0) {
AttrResponseVo attrResponseVo = JSON.parseObject(JSON.toJSONString(r.get("attr")), new TypeReference<AttrResponseVo>() {
});
navVo.setNavName(attrResponseVo.getAttrName());
}
} catch (Exception e) {
log.error("远程调用商品服务查询属性失败", e);
}
//6.3 设置面包屑跳转链接
String queryString = searchParamVo.get_queryString();
String replace = queryString.replace("&attrs=" + attr, "").replace("attrs=" + attr + "&", "").replace("attrs=" + attr, "");
navVo.setLink("http://search.gulishop.com/search.html" + (replace.isEmpty() ? "" : "?" + replace));
return navVo;
}).collect(Collectors.toList());
result.setNavs(navVos);
}
return result;
}
二、CompletableFuture异步编排
概念
- 线程、JUC知识见往期博客:JUC
- 多个任务可以交给线程池来异步执行,但是多个待执行的任务之间部分有逻辑先后的顺序,因此需要异步编排
- CompletableFuture< T > 实现 Future< T > 接口,可以通过他完成异步编排,类似前端的Promise,可以一直then(…)
- Future 是 Java 5 添加的类,用来描述一个异步计算的结果。你可以使用
isDone
方法检查计
算是否完成,或者使用get
阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel
方法停止任务的执行。 - 虽然
Future
以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不
方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的
初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果,为
什么不能用观察者设计模式当计算结果完成及时通知监听者呢? - 很多语言,比如 Node.js,采用回调的方式实现异步编程。Java 的一些框架,比如 Netty,自
己扩展了 Java 的Future
接口,提供了addListener
等多个扩展方法;Google guava 也提供了
通用的扩展 Future;Scala 也提供了简单易用且功能强大的 Future/Promise 异步编程模式。
作为正统的 Java 类库,是不是应该做点什么,加强一下自身库的功能呢? - 在 Java 8 中, 新增加了一个包含 50 个方法左右的类:
CompletableFuture
,提供了非常强大的
Future 的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以
通过回调的方式处理计算结果,并且提供了转换和组合 CompletableFuture 的方法。
CompletableFuture 类实现了 Future 接口,所以你还是可以像以前一样通过get
方法阻塞或
者轮询的方式获得结果,但是这种方式不推荐使用。
CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果 - 使用Callable < T > + FutureTask< T > 创建线程时,后者也是Future< T > 的子实现接口
使用
- CompletableFuture 提供了四个静态方法来创建一个异步操作
- runXxxx 都是没有返回结果的,supplyXxx 都是可以获取返回结果的
- 可以传入自定义的线程池,否则就用默认的线程池
1.whenComplete完成时回调
- 可以在异步任务的下面写业务代码,也可以放到成功回调里面,因为
CompletableFuture.runAsync()
和CompletableFuture.supplyAsync()
执行完之后还会返回completableFuture
接着调方法 - whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况。
- whenComplete 和 whenCompleteAsync 的区别:
- whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
- whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
- 方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
举栗
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(new Supplier<Object>() {
@Override
public Object get() {
System.out.println(Thread.currentThread().getName() + "completableFuture");
int i = 10 / 0;
return 1024;
}
}).whenComplete(new BiConsumer<Object, Throwable>() {
@Override
public void accept(Object o, Throwable throwable) {
System.out.println("-------o=" + o.toString());
System.out.println("-------throwable=" + throwable);
}
}).exceptionally(new Function<Throwable, Object>() {
@Override
public Object apply(Throwable throwable) {
System.out.println("throwable=" + throwable);
return 6666;
}
});
System.out.println(future.get());
}
注意前面的只能感知异常,如果无异常不能对原结果在进行处理
2.thenApply线程串化方法
概念
- 简单理解就是将线程顺序串起来,一个线程可能依赖另外一个线程的结果
- 当前线程执行完接着可以执行另外一个任务,当前线程的结果可被消费,消费之后结果可以接着向下传递
- thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前
任务的返回值。 - thenAccept 方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
- thenRun 方法:只要上面的任务执行完成,就开始执行 thenRun,只是处理完任务后,执行 thenRun 的后续操作。
- 带有 Async 默认是异步执行的。同之前。
以上都要前置任务成功完成。Function<? super T,? extends U>
- T:上一个任务返回结果的类型
- U:当前任务的返回值类型
3.两任务组合 - 都要完成
两个任务必须都完成,触发该任务。
- thenCombine:组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
- thenAcceptBoth:组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值。
- runAfterBoth:组合两个 future,不需要获取 future 的结果,只需两个 future 处理完任务后,处理该任务
4.两任务组合 - 一个完成
当两个任务中,任意一个 future 任务完成的时候,执行任务。
- applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值。
- acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值。
- runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返
回值
5.多任务组合
- allOf:等待所有任务完成
- anyOf:只要有一个任务完成
三、商品详情
点击商品进入详情页,设置网关路由
复制前端静态页面,修改页面,编写后端查询代码
1.完成异步编排
自定义线程池
ThreadPoolConfigProperties 绑定properties配置文件,后续可以在配置文件设置线程池参数
package henu.soft.xiaosi.product.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "gulishop.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
private int corePoolSize;
private int maxPoolSize;
private long keepAliveTime;
}
MyThreadPoolConfig 自定义线程池
package henu.soft.xiaosi.product.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Configuration
//@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
public class MyThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties properties) {
return new ThreadPoolExecutor(
properties.getCorePoolSize(),
properties.getMaxPoolSize(),
properties.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10000),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
# 自定义线程池
gulishop.thread.corePoolSize=10
gulishop.thread.maxPoolSize=20
gulishop.thread.keepAliveTime=300
使用
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
//1、sku基本信息的获取 pms_sku_info
SkuInfoEntity skuInfoEntity = this.getById(skuId);
skuItemVo.setInfo(skuInfoEntity);
return skuInfoEntity;
}, executor);
//2、sku的图片信息 pms_sku_images
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> skuImagesEntities = skuImagesService.list(new QueryWrapper<SkuImagesEntity>().eq("sku_id", skuId));
skuItemVo.setImages(skuImagesEntities);
}, executor);
//3、获取spu的销售属性组合-> 依赖1 获取spuId
CompletableFuture<Void> saleFuture = infoFuture.thenAcceptAsync((info) -> {
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.listSaleAttrs(info.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
//4、获取spu的介绍-> 依赖1 获取spuId
CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((info) -> {
SpuInfoDescEntity byId = spuInfoDescService.getById(info.getSpuId());
skuItemVo.setDesc(byId);
}, executor);
//5、获取spu的规格参数信息-> 依赖1 获取spuId catalogId
CompletableFuture<Void> attrFuture = infoFuture.thenAcceptAsync((info) -> {
List<SpuItemAttrGroupVo> spuItemAttrGroupVos=productAttrValueService.getProductGroupAttrsBySpuId(info.getSpuId(), info.getCatalogId());
skuItemVo.setGroupAttrs(spuItemAttrGroupVos);
}, executor);
// //6、秒杀商品的优惠信息
// CompletableFuture<Void> seckFuture = CompletableFuture.runAsync(() -> {
// R r = seckillFeignService.getSeckillSkuInfo(skuId);
// if (r.getCode() == 0) {
// SeckillSkuVo seckillSkuVo = r.getData(new TypeReference<SeckillSkuVo>() {
// });
// long current = System.currentTimeMillis();
// //如果返回结果不为空且活动未过期,设置秒杀信息
// if (seckillSkuVo != null&¤t<seckillSkuVo.getEndTime()) {
// skuItemVo.setSeckillSkuVo(seckillSkuVo);
// }
// }
// }, executor);
//等待所有任务执行完成
try {
CompletableFuture.allOf(imageFuture, saleFuture, descFuture, attrFuture).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return skuItemVo;
}
/**
* 商品详情页
*
* @param skuId
* @return
*/
/*
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
//1、sku基本信息的获取 pms_sku_info
SkuInfoEntity skuInfoEntity = this.getById(skuId);
skuItemVo.setInfo(skuInfoEntity);
Long spuId = skuInfoEntity.getSpuId();
Long catalogId = skuInfoEntity.getCatalogId();
//2、sku的图片信息 pms_sku_images
List<SkuImagesEntity> skuImagesEntities = skuImagesService.list(new QueryWrapper<SkuImagesEntity>().eq("sku_id", skuId));
skuItemVo.setImages(skuImagesEntities);
//3、获取spu的销售属性组合-> 依赖1 获取spuId
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.listSaleAttrs(spuId);
skuItemVo.setSaleAttr(saleAttrVos);
//4、获取spu的介绍-> 依赖1 获取spuId
SpuInfoDescEntity byId = spuInfoDescService.getById(spuId);
skuItemVo.setDesc(byId);
//5、获取spu的规格参数信息-> 依赖1 获取spuId catalogId
List<SpuItemAttrGroupVo> spuItemAttrGroupVos = productAttrValueService.getProductGroupAttrsBySpuId(spuId, catalogId);
skuItemVo.setGroupAttrs(spuItemAttrGroupVos);
//TODO 6、秒杀商品的优惠信息
return skuItemVo;
}
*/