商城搜索页-检索服务
搜索已经上架商品的信息
根据用户输入条件进行检索
检索条件分析
封装页面所有可能传递过来的查询条件
1.全文检索:skuTitle -> keyword
2.排序:saleCount,hotScore,skuPrice
3.过滤:hasStock,skuPrice,brandID,catalog3Id,attrs
4.聚合:attrs
public class SearchParam {
/**
* 关键字,页面传过来的全为匹配字段(skuTitle)
*/
private String keyword;
/**
* 三级分类 id
*/
private Long catalog3Id;
/**
* 排序条件
* saleCount,hotScore,skuPrice
* saleCount_desc or saleCount_asc
*/
private String sort;
/**
* 是否只显示有货
*/
private Integer hasStock;
/**
* sku价格
*/
private String skuPrice;
/**
* 品牌Id列表
*/
private List<Long> brandId;
/**
* 属性 attrs=1_其他:安卓&attrs=2_六寸:五寸:七寸
* 属性id_属性值:属性值
*/
private List<String> attrs;
/**
* 页面号
*/
private Integer pageNum;
}
返回页面的数据
@Data
public class SearchResData {
/**
* 查询到的所有的商品信息
*/
private List<SkuEsModel> products;
/**
* 页面号
*/
private Integer pageNum;
/**
* 总共有多少条数据
*/
private Long total;
/**
* 总页数
*/
private Integer totalPage;
/**
* 当前结果查询到的所有品牌
*/
private List<BrandVo> brands;
/**
* 当前结果查询到的所有属性
*/
private List<AttrVo> attrs;
/**
* 当前结果查询到的所有分类信息
*/
private List<CatalogVo> catalogs;
@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> attrValues;
}
@Data
public static class CatalogVo{
private Long catalogId;
private String catalogName;
}
}
接口实现
@Controller
public class SearchController {
@Autowired
private MallSearchService mallSearchService;
/**
* 在es中搜索已经上架商品的信息
*
* @param param 参数
* @param model 模型
* @return {@link String}* @throws IOException ioexception
*/
@GetMapping("/list.html")
public String list(SearchParam param, Model model) throws IOException {
SearchResData data = mallSearchService.search(param);
model.addAttribute("result", data);
return "list";
}
}
业务方法
@Service
public class MallSearchServiceImpl implements MallSearchService {
@Resource
private RestHighLevelClient esClient;
@Override
public SearchResData search(SearchParam param) throws IOException {
// 1.准备检索请求
SearchRequest searchRequest = buildSearchConditions(param);
// 2.执行检索
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
// 3.封装对象
return getData(response, param);
}
/**
* 构建搜索条件
*
* @param param 参数
* @return {@link SearchRequest}
*/
private SearchRequest buildSearchConditions(SearchParam param) {
// 构建dsl查询语句
SearchSourceBuilder searchSource = SearchSourceBuilder.searchSource();
// 1.构建boolQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.must keyword
if (StringUtils.isNoneBlank(param.getKeyword())) {
boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
}
// 3.filter 过滤
// 三级分类id
if (param.getCatalog3Id() != null) {
boolQuery.filter(QueryBuilders.matchQuery("catalogId", param.getCatalog3Id()));
}
// 品牌id
if (param.getBrandId() != null && param.getBrandId().size() > 0) {
boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
}
// 查询是否有库存
if (param.getHasStock() != null) {
boolQuery.filter(QueryBuilders.termsQuery("hasStock", param.getHasStock() == 1));
}
// 价格区间
if (StringUtils.isNoneBlank(param.getSkuPrice())) {
// 1_500 _500 500_
String[] s = param.getSkuPrice().split("_");
RangeQueryBuilder query = QueryBuilders.rangeQuery("skuPrice");
if (s.length == 2) {
query.gte(s[0]).lte(s[1]);
} else if (s.length == 1) {
if (param.getSkuPrice().startsWith("_")) {
query.lte(s[0]);
} else if (param.getSkuPrice().endsWith("_")) {
query.gte(s[0]);
}
}
boolQuery.filter(query);
}
// 属性
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
for (String attrStr : param.getAttrs()) { // attrs=1_其他:安卓&attrs=2_六寸:五寸:七寸 属性id_属性值:属性值
BoolQueryBuilder bool = QueryBuilders.boolQuery();
String[] s = attrStr.split("_");
// 获取需要查询的属性的信息
String attrId = s[0];
String[] attrValues = s[1].split(":");
bool.must(QueryBuilders.matchQuery("attrs.attrId", attrId));
bool.must(QueryBuilders.termsQuery("attrs.attrName", attrValues));
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", bool, ScoreMode.None);
boolQuery.filter(nestedQueryBuilder);
}
}
// 4.排序
if (StringUtils.isNoneBlank(param.getSort())) { // saleCount,hotScore,skuPrice saleCount_desc or saleCount_asc
String[] s = param.getSort().split("_");
String field = s[0];
String sort = s[1];
// 构建排序条件
SortOrder sortOrder = SortOrder.ASC;
if (sort.equals("desc"))
sortOrder = SortOrder.DESC;
searchSource.sort(field, sortOrder);
}
// 5.分页
searchSource.size(PRODUCT_PAGE_SIZE)
.from((param.getPageNum() - 1) * PRODUCT_PAGE_SIZE);
// 6.高亮
if (StringUtils.isNoneBlank(param.getKeyword())) {
searchSource.highlighter(SearchSourceBuilder.highlight()
.field("skuTitle")
.preTags("<b style='color:red'>").postTags("</b>"));
}
// 7.聚合分析
TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId").size(50); // 找出每一个品牌的id
brandAgg.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1)); // 名字
brandAgg.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1)); // 图片
searchSource.aggregation(brandAgg);
TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(50); // 找出每一个分类的id
catalogAgg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(1)); // 名字
catalogAgg.subAggregation(AggregationBuilders.terms("catalogImgAgg").field("catalogImg").size(1)); // 图片
searchSource.aggregation(catalogAgg);
NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs"); // 找出属性的相关信息
AggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"));
attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"));
attrAgg.subAggregation(attrIdAgg);
searchSource.aggregation(attrAgg);
searchSource.query(boolQuery);
return new SearchRequest().source(searchSource).indices(EsConstant.PRODUCT_INDEX);
}
/**
* 返回页面需要的数据
*
* @param response 响应
* @param param
* @return {@link SearchResData}
*/
private SearchResData getData(SearchResponse response, SearchParam param) {
SearchResData data = new SearchResData();
// 准备数据
// 封装product需要的数据
SearchHits hits = response.getHits();
SearchHit[] hitsHits = hits.getHits();
// 封装其他需要的数据
Aggregations aggregations = response.getAggregations();
List<SkuEsModel> skuEsModels = new ArrayList<>();
List<SearchResData.BrandVo> brandVos = new ArrayList<>();
List<SearchResData.AttrVo> attrVos = new ArrayList<>();
List<SearchResData.CatalogVo> catalogVos = new ArrayList<>();
// 设置产品总信息
for (SearchHit h : hitsHits) {
Map<String, Object> source = h.getSourceAsMap();
SkuEsModel skuEsModel = BeanUtil.mapToBean(source, SkuEsModel.class, true);
// 设置高亮
if (StringUtils.isNotBlank(param.getKeyword())) {
skuEsModel.setSkuTitle(String.valueOf(h.getHighlightFields().get("skuTitle")));
}
skuEsModels.add(skuEsModel);
}
// 设置分类
ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
SearchResData.CatalogVo catalogVo = new SearchResData.CatalogVo();
ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalogNameAgg");
catalogVo.setCatalogId((Long) bucket.getKey());
catalogVo.setCatalogName(catalogNameAgg.getBuckets().get(0).getKeyAsString());
catalogVos.add(catalogVo);
}
// 设置品牌
ParsedLongTerms brandAgg = aggregations.get("brandAgg");
for (Terms.Bucket bucket : brandAgg.getBuckets()) {
SearchResData.BrandVo brandVo = new SearchResData.BrandVo();
ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brandImgAgg");
ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
brandVo.setBrandId((Long) bucket.getKeyAsNumber());
brandVo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());
brandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());
brandVos.add(brandVo);
}
// 设置属性
ParsedNested attrAgg = aggregations.get("attrAgg");
ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
SearchResData.AttrVo attrVo = new SearchResData.AttrVo();
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
attrVo.setAttrId((Long) bucket.getKey());
attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
attrVo.setAttrValue(attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList()));
attrVos.add(attrVo);
}
data.setProducts(skuEsModels);
data.setBrands(brandVos);
data.setAttrs(attrVos);
data.setCatalogs(catalogVos);
data.setPageNum(param.getPageNum());
data.setTotal(hits.getTotalHits().value);
data.setTotalPage(Math.toIntExact(
hits.getTotalHits().value % PRODUCT_PAGE_SIZE == 0
? hits.getTotalHits().value / PRODUCT_PAGE_SIZE
: (hits.getTotalHits().value / PRODUCT_PAGE_SIZE) + 1));
return data;
}
}
添加面包屑导航功能
SearchParam
SearchResData
业务方法
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
List<SearchResData.NavVo> navVos = param.getAttrs().stream().map(i -> {
// 分析每个attrs传过来查询的数值;
SearchResData.NavVo navVo = new SearchResData.NavVo();
String[] s = i.split("_");
navVo.setNavValue(s[1]);
R r = productServiceFeign.attrInfo(Long.valueOf(s[0]));
if (r.getCode() == 0) {
AttrRespVo attr = (AttrRespVo) r.get("attr");
navVo.setNavName(attr.getAttrName());
}
// 取消这个面包屑后,我们要跳转到哪个地方,将请求地址置空
String replace = param.get_queryString().replace("attrs=" + Arrays.toString(s), "");
navVo.setLink("http://localhost:8889/list.html?" + replace);
return navVo;
}).collect(Collectors.toList());
data.setNavs(navVos);
}
异步编排
completableFuture
启动异步编排任务
public class ThreadTest {
static ExecutorService executor = new ThreadPoolExecutor(10, 100 * 100, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main-start");
// 沒有返回值
CompletableFuture<Void> voidThread = CompletableFuture.runAsync(() -> {
System.out.println("当前线程" + Thread.currentThread().getName());
int i = 10 / 2;
System.out.println("结果:" + i);
}, executor);
// 有返回值
CompletableFuture<Integer> intThread = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程" + Thread.currentThread().getName());
return 100 / 2;
}, executor);
System.out.println("结果"+intThread.get());
System.out.println("main-end");
}
}
任务完成时回调方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main-start");
CompletableFuture<Integer> fu = CompletableFuture
.supplyAsync(() -> 10, executor)
// 当上面的任务完成后调用
.whenComplete((res, ex) -> System.out.println(res))
// 出现异常后
.exceptionally(ex-> 100);
System.out.println(fu.get());
System.out.println("main-end");
}
任务完成后对结果进行修改
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main-start");
CompletableFuture<Integer> handle = CompletableFuture
.supplyAsync(() -> {
System.out.println("开始执行业务");
System.out.println("执行完毕");
return 10;
}, executor)
.handle((res, e) -> {
res = 20;
return res;
});
System.out.println(handle.get());
System.out.println("main-end");
}
线程串行化
两个任务组合-都要完成
商品详情页
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo res = new SkuItemVo();
// 1.sku基本信息
SkuInfoEntity skuInfo = getById(skuId);
res.setSkuInfoEntity(skuInfo);
Long spuId = skuInfo.getSpuId();
Long catalogId = skuInfo.getCatalogId();
// 2.sku的图片信息
List<SkuImagesEntity> images = skuImagesService.getBySkuId(skuId);
res.setImages(images);
// 3.spu的销售属性
List<SkuItemSaleAttrVo> saleAttrVo = skuSaleAttrValueService.getSpuId(skuId);
res.setSaleAttrs(saleAttrVo);
// 4.spu的介绍
SpuInfoDescEntity desc = spuInfoDescService.getById(spuId);
res.setDesc(desc);
// 5.spu的规格参数
List<SpuItemAttrGroupVo> groupVos = attrGroupService.getBySpuId(spuId,catalogId);
res.setGroupAttrs(groupVos);
return res;
}
异步编排后
引入配置提示jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
配置资源类
@ConfigurationProperties(prefix = "thread.pool")
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAlive;
}
配置类
@Configuration
public class ThreadConfig {
@Bean
public ThreadPoolExecutor threadPool(ThreadPoolConfigProperties poolConfig) {
return new ThreadPoolExecutor(poolConfig.getCoreSize(), poolConfig.getMaxSize(), poolConfig.getKeepAlive(), TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10000), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
}
配置
异步编排后
@Autowired
ThreadPoolExecutor threadPool;
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo res = new SkuItemVo();
AtomicReference<Long> spuId = new AtomicReference<>();
AtomicReference<Long> catalogId = new AtomicReference<>();
CompletableFuture<SkuInfoEntity> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
// 1.sku基本信息
SkuInfoEntity skuInfo = getById(skuId);
res.setSkuInfoEntity(skuInfo);
spuId.set(skuInfo.getSpuId());
catalogId.set(skuInfo.getCatalogId());
return skuInfo;
}, threadPool);
CompletableFuture<Void> spuSaleFuture = skuInfoFuture.thenAcceptAsync((r) -> {
// 3.spu的销售属性
List<SkuItemSaleAttrVo> saleAttrVo = skuSaleAttrValueService.getSpuId(skuId);
res.setSaleAttrs(saleAttrVo);
}, threadPool);
CompletableFuture<Void> spuDescFuture = skuInfoFuture.thenAcceptAsync((r) -> {
// 4.spu的介绍
SpuInfoDescEntity desc = spuInfoDescService.getById(spuId);
res.setDesc(desc);
}, threadPool);
CompletableFuture<Void> spuAttrFuture = skuInfoFuture.thenAcceptAsync((r) -> {
// 5.spu的规格参数
List<SpuItemAttrGroupVo> groupVos = attrGroupService.getBySpuId(spuId.get(), catalogId.get());
res.setGroupAttrs(groupVos);
}, threadPool);
CompletableFuture<Void> imagesFuture = CompletableFuture.runAsync(() -> {
// 2.sku的图片信息
List<SkuImagesEntity> images = skuImagesService.getBySkuId(skuId);
res.setImages(images);
}, threadPool);
try {
CompletableFuture.allOf(imagesFuture, skuInfoFuture, spuAttrFuture, spuDescFuture, spuSaleFuture).get();
} catch (Exception e) {
e.printStackTrace();
}
return res;
}