目录
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. 引入依赖
2. 将页面复制到gulimall-search的src/main/resources/templates/目录下
3. 调整搜索页面
1)引入thymeleaf
xmlns:th="http://www.thymeleaf.org"
2)修改页面静态资源的引入,加上/static/search
eg:
由
改为:
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/
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-devtools
true
引入thymeleaf,修改引入静态资源的路径以/static/search开始。以下为了举例,list.html完整代码见 1.9.1 检索页面完整代码
Document
...
(1)以管理员身份运行SwitchHosts;
(2)让所有的search.gulimall.com可以定位到linux的nginx服务
(1)浏览器访问http://localhost:12000
(2)浏览器访问http://search.gulimall.com/
(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
- 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
http://search.gulimall.com/
注意静态资源的路径是否正确;有些图片可能缺失,影响不大。
前面已经引入。
页面修改后同过Ctrl+Shift+F9对html页面进行重新构建,无需重启项目
spring:
thymeleaf:
cache: false
效果要求:点击搜索页面左上角的谷粒商城首页或谷粒商城都能跳转到商城首页。如下图:
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. 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. 重启服务测试
完整查询参数
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_高清屏
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 brandId;// 按照品牌进行查询,可以多选
private List attrs;// 按照属性进行筛选
private Integer pageNum;// 页码
}
gulimall-search/src/main/java/com/wen/gulimall/search/vo/SearchResult.java
@Data
public class SearchResult {
// 查询到的所有商品信息
private List products;
/**
* 以下是分页信息
*/
private Integer pageNum; // 当前页码
private Long total; // 总记录数
private Integer totalPages; // 总页码
private List brands; // 当前查询到的结果,所有涉及到的品牌
private List catalogs; // 当前查询到的结果,所有涉及到的所有分类
private List 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 attrValue;
}
}
# 查询部分
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
}
}
}
]
}
}
}
模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
# 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
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": "",
"post_tags": ""
}
}
1. mapping映射参数index、doc_values使用说明:
具体可以参照官网:mapping parameters
2. 错误原因:
brandName属性的doc_values为false不可以进行聚合。
更新映射,删除映射中的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)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
}
}
}
}
}
}
}
}
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": "",
"post_tags": ""
},
"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
}
}
}
}
}
}
}
}
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";
}
}
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("");
builder.postTags("");
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分析&封装
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;
}
根据代码中System.out.println("构建的DSL语句"+sourceBuilder.toString());输出的DSL语句,在Kibana中进行测试,看输出结果是否正确。
对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 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 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 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 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 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的值设置高亮。
通过debug查看结果的封装。
(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 brandIds){
List brand = brandService.getBrandsByIds(brandIds);
return R.ok().put("brand", brand);
}
2)编写远程调用接口
@GetMapping("/product/brand/infos")
public R brandsInfo(@RequestParam("brandIds") List brandIds);
(7)注意th:if的优先级比th:with高,可以参照 usingthymeleaf.pdf -》 Attribute Precedence
Document
我的购物车
0
购物车中还没有商品,赶紧选购吧!
-
家用电器
-
手机 / 运营商 / 数码
手机通讯 >
-
手机
对讲机
手机维修
以旧换新
运营商 >
-
合约机
固话宽带
办套餐
从话费/流量
中国电信
中国移动
中国联通
谷粒商城通信
170选号
手机配件 >
-
手机壳
贴膜
手机储存卡
数据线
存电器
手机耳机
创业配件
手机饰品
手机电池
苹果周边
移动电源
蓝牙耳机
手机支架
车载配件
拍照配件
摄影摄像 >
-
数码相机
单电/微单相机
单反相机
拍立得
运动相机
摄像机
镜头
户外器材
影棚器材
冲印服务
数码相框
数码配件 >
-
三脚架/云台
相机包
滤镜
散光灯/手柄
相机清洁
机身附件
镜头附件
读卡器
支架
电池/存电器
影音娱乐 >
-
耳机/耳麦
音箱/音响
智能音箱
便携/无线音箱
收音机
麦克风
MP3/MP4
专业音频
智能设备 >
-
智能手环
智能手表
智能眼镜
智能机器人
运动跟踪器
健康监测
智能配饰
智能家居
体感车
无人机
其他配件
电子教育 >
-
学生平板
点读机
早教益智
录音笔
电纸书
电子词典
复读机
-
电脑 / 办公
-
家居 / 家具 / 家装 / 厨具
-
男装 / 女装 / 童装 / 内衣
-
美妆个护 / 宠物
-
女鞋 / 箱包 / 钟表 / 珠宝
-
男鞋 / 运动 / 户外
-
汽车 / 汽车用品
-
母婴 / 玩具乐器
-
食品 / 酒类 / 生鲜 / 特产
-
礼品鲜花 / 农资绿植
-
医药保健 / 计生情趣
-
图书 / 音箱/ 电子书
-
机票 / 酒店 / 旅游 / 生活
-
理财 / 众筹 / 白条 / 保险
热卖推荐
促销活动
- . 红米千元全面屏手机上市
- . 锤子坚果Pro2火爆预约中
- . 大牌新品 疯狂抢购
- . X20 vivo蓝新色上市
- . 荣耀畅玩7X新品上市
商品精选
猜你喜欢
换一批
-
¥1999.00
-
¥1999.00
-
¥1999.00
-
¥1999.00
-
¥1999.00
-
¥1999.00
我的足迹
更多浏览记录
用于面包屑导航,远程调用商品服务,根据ID获取名称。注意要引入spring-cloud依赖管理,以及版本控制。
org.springframework.cloud
spring-cloud-starter-openfeign
包 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 brandId;// 按照品牌进行查询,可以多选
private List attrs;// 按照属性进行筛选
private Integer pageNum = 1;// 页码
private String _queryString; // 原生的所有查询条件
}
@Data
public class SearchResult {
// 查询到的所有商品信息
private List products;
/**
* 以下是分页信息
*/
private Integer pageNum; // 当前页码
private Long total; // 总记录数
private Integer totalPages; // 总页码
private List pageNavs; // 页码导航栏
private List brands; // 当前查询到的结果,所有涉及到的品牌
private List catalogs; // 当前查询到的结果,所有涉及到的所有分类
private List attrs; // 当前查询到的结果,所有涉及到的所有属性
//========================以上是返回给页面的所有信息==========================
// 面包屑导航数据
private List navs = new ArrayList<>();
private List 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 attrValue;
}
}
@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";
}
}
@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("");
builder.postTags("");
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 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 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 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 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 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 pageNavs = new ArrayList<>();
for(int i = 1;i<=totalPages;i++){
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
// 6. 构建面包屑导航功能 - 属性
if(CollectionUtil.isNotEmpty(searchParam.getAttrs())) {
List 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() {
});
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 navs = result.getNavs();
SearchResult.NavVo navVo = new SearchResult.NavVo();
navVo.setNavName("品牌");
// TODO 远程查询所有品牌
R r = productFeignService.brandsInfo(searchParam.getBrandId());
if(r.getCode() == 0){
List brand = r.getData("brand", new TypeReference>() {
});
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;
}
}
开启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 brandIds);
}
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().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;
}
gulimall-product/src/main/java/com/wen/gulimall/product/app/BrandController.java
@GetMapping("/infos")
public R info(@RequestParam("brandIds") List brandIds){
List brand = brandService.getBrandsByIds(brandIds);
return R.ok().put("brand", brand);
}
gulimall-product/src/main/java/com/wen/gulimall/product/service/BrandService.java
List getBrandsByIds(List brandIds);
gulimall-product/src/main/java/com/wen/gulimall/product/service/impl/BrandServiceImpl.java
@Override
public List getBrandsByIds(List brandIds) {
return baseMapper.selectList(new QueryWrapper().in("brand_id",brandIds));
}