学习目标:
注意:第八章中增加分值的方法,要将ES中的分之设置为Long类型
商品检索流程:
利用dsl 语句查询es 数据
GET goods/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "redmi"
}
}
],
"filter": [
{
"term": {
"tmId": "1"
}
},
{
"term": {
"category3Id": "61"
}
},
{
"bool": {
"must": [
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "24"
}
}
},
{
"term": {
"attrs.attrValue": {
"value": "128G"
}
}
}
]
}
}
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "115"
}
}
},
{
"term": {
"attrs.attrValue": {
"value": "4500-5000mAh"
}
}
}
]
}
}
}
}
]
}
}
]
}
},
"aggs": {
"tmIdAgg": {
"terms": {
"field": "tmId"
},
"aggs": {
"tmNameAgg": {
"terms": {
"field": "tmName",
"size": 1
}
},
"tmLogoUrlAgg": {
"terms": {
"field": "tmLogoUrl",
"size": 10
}
}
}
},
"attrAgg":{
"nested": {
"path": "attrs"
},
"aggs": {
"attrIdAgg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},
"aggs": {
"attrNameAgg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attrValueAgg": {
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
搜索参数实体:SearchParam
package com.atguigu.gmall.list.model;
import lombok.Data;
// 封装查询条件
// ?category3Id=61&trademark=2:华为&props=23:4G:运行内存&order=1:desc
@Data
public class SearchParam {
private Long category1Id;;//三级分类id
private Long category2Id;
private Long category3Id;
// trademark=2:华为
private String trademark;//品牌
private String keyword;//检索的关键字
// 排序规则
// 1:hotScore 2:price
private String order = ""; // 1:综合排序/热度 2:价格
// props=23:4G:运行内存
//平台属性Id 平台属性值名称 平台属性名
private String[] props;//页面提交的数组
private Integer pageNo = 1;//分页信息
private Integer pageSize = 3; // 每页默认显示的条数
}
检索结果:SearchResponseVo
package com.atguigu.gmall.list.model;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// 总的数据
@Data
public class SearchResponseVo implements Serializable {
//品牌 此时vo对象中的id字段保留(不用写) name就是“品牌” value: [{id:100,name:华为,logo:xxx},{id:101,name:小米,log:yyy}]
private List<SearchResponseTmVo> trademarkList;
//所有商品的顶头显示的筛选属性
private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
//检索出来的商品信息
private List<Goods> goodsList = new ArrayList<>();
private Long total;//总记录数
private Integer pageSize;//每页显示的内容
private Integer pageNo;//当前页面
private Long totalPages;
}
结果集品牌实体:SearchResponseTmVo
package com.atguigu.gmall.list.model;
import lombok.Data;
import java.io.Serializable;
// 品牌数据
@Data
public class SearchResponseTmVo implements Serializable {
//当前属性值的所有值
private Long tmId;
//属性名称
private String tmName;//网络制式,分类
//图片名称
private String tmLogoUrl;//网络制式,分类
}
结果集平台属性实体:SearchResponseAttrVo
package com.atguigu.gmall.list.model;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// 平台属性相关对象
@Data
public class SearchResponseAttrVo implements Serializable {
// 平台属性Id
private Long attrId;//1
//当前属性值的集合
private List<String> attrValueList = new ArrayList<>();
//属性名称
private String attrName;//网络制式,分类
}
/**
* 商品检索
*
* @param searchParam
* @return
*/
@PostMapping("/inner")
public Result search(@RequestBody SearchParam searchParam) {
SearchResponseVo responseVo = searchService.search(searchParam);
return Result.ok(responseVo);
}
/**
* 商品检索
*
* @param searchParam
* @return
*/
SearchResponseVo search(SearchParam searchParam);
SearchServiceImpl
package com.atguigu.gmall.list.service.impl;
import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.model.*;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.model.BaseAttrInfo;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.atguigu.gmall.product.model.BaseTrademark;
import com.atguigu.gmall.product.model.SkuInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author: atguigu
* @create: 2023-01-06 15:37
*/
@Slf4j
@Service
@SuppressWarnings("all")
public class SearchServiceImpl implements SearchService {
@Autowired
private ProductFeignClient productFeignClient;
@Autowired
private RestHighLevelClient restHighLevelClient;
@Autowired
private RedisTemplate redisTemplate;
/**
* 常量:索引库名称
*/
private static final String INDEX_NAME = "goods";
/**
* 1.远程调用商品微服务获取商品相关信息
* 2.封装索引库商品文档Goods
* 3.调用ES将文档对象存入索引库
*
* @param skuId
*/
@Override
public void upperGoods(Long skuId) {
try {
//1.创建索引库文档对象:Goods
Goods goods = new Goods();
//2.封装商品文档对象Goods中的属性赋值
//2.1 根据SkuID远程查询SkuInfo商品信息
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
if (skuInfo != null) {
goods.setId(skuInfo.getId());
goods.setTitle(skuInfo.getSkuName());
goods.setCategory3Id(skuInfo.getCategory3Id());
goods.setPrice(skuInfo.getPrice().doubleValue());
goods.setDefaultImg(skuInfo.getSkuDefaultImg());
goods.setCreateTime(new Date());
goods.setCreatedDate(skuInfo.getCreateTime());
//2.2 根据skuInfo的品牌ID查询品牌信息
BaseTrademark trademark = productFeignClient.getTrademarkById(skuInfo.getTmId());
if (trademark != null) {
goods.setTmId(trademark.getId());
goods.setTmName(trademark.getTmName());
goods.setTmLogoUrl(trademark.getLogoUrl());
}
//2.3 根据分类ID查询分类信息
BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
if (categoryView != null) {
goods.setCategory1Id(categoryView.getCategory1Id());
goods.setCategory1Name(categoryView.getCategory1Name());
goods.setCategory2Id(categoryView.getCategory2Id());
goods.setCategory2Name(categoryView.getCategory2Name());
goods.setCategory3Id(categoryView.getCategory3Id());
goods.setCategory3Name(categoryView.getCategory3Name());
}
//2.4 根据skuID查询平台属性以及值
List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
if (!CollectionUtils.isEmpty(attrList)) {
List<SearchAttr> attrs = attrList.stream().map(baseAttrInfo -> {
SearchAttr searchAttr = new SearchAttr();
searchAttr.setAttrId(baseAttrInfo.getId());
searchAttr.setAttrName(baseAttrInfo.getAttrName());
searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
return searchAttr;
}).collect(Collectors.toList());
goods.setAttrs(attrs);
}
//3.将索引库文档对象存入索引库ES
//3.1 创建IndexRequest对象 封装索引库名称 文档ID 请求JSON
IndexRequest indexRequest = new IndexRequest(INDEX_NAME)
.id(skuInfo.getId().toString())
.source(JSON.toJSONString(goods), XContentType.JSON);
//3.2 执行保存
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
}
} catch (IOException e) {
e.printStackTrace();
log.error("商品上架失败:{}", e);
}
}
/**
* 删除商品文档
*
* @param skuId
*/
@Override
public void lowerGoods(Long skuId) {
try {
DeleteRequest request = new DeleteRequest(
INDEX_NAME,
skuId.toString());
restHighLevelClient.delete(request, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
log.error("商品下架失败:{}", e);
}
}
/**
* 更新商品的热度排名分值
*
* @param skuId
* @return
*/
@Override
public void incrHotScore(Long skuId) {
try {
//1.更新Redis缓存中商品热点排名分值 分值+1
//1.1 构建ZSet排名Key
String hotKey = "hotScore";
//1.2 调用自增分值+1方法为查询商品增加分值
String skuIdStr = skuId.toString();
Double score = redisTemplate.opsForZSet().incrementScore(hotKey, skuIdStr, 1);
//2.满足更新索引库条件后再更新文档中分值
if (score % 10 == 0) {
//2.0 根据文档ID查询商品文档对象
Map<String, Object> jsonMap = new HashMap<>();
//注意:索引库中存储是Long类型 将分值有Double转为Long
jsonMap.put("hotScore", score.longValue());
//2.1 创建更新文档对象 UpdateRequest
UpdateRequest updateRequest = new UpdateRequest(INDEX_NAME, skuIdStr);
updateRequest.doc(jsonMap);
//2.2 更新文档
restHighLevelClient.update(
updateRequest, RequestOptions.DEFAULT);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从索引库goods中查询业务数据同时,聚合过滤条件
*
* @param searchParam
* @return
*/
@Override
public SearchResponseVo search(SearchParam searchParam) {
try {
//一、构建SearchRequest 封装 检索索引库名称 请求体包含:查询方式、分页、高亮、排序、聚合
SearchRequest searchRequest = this.buiderDSL(searchParam);
//二、执行检索结果
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//三、封装响应结果 返回vo对象
SearchResponseVo responseVo = this.parseResult(response, searchParam);
return responseVo;
} catch (IOException e) {
e.printStackTrace();
}
return new SearchResponseVo();
}
/**
* 设置DSL请求路径以及请求体参数
*
* @param searchParam
* @return
*/
private SearchRequest buiderDSL(SearchParam searchParam) {
//1.创建SearchRequest对象 指定查询索引库名称
SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
//2.创建SearchSourceBuilder 封装:"query"查询方式 "from","size"分页 "highlight"高亮 "sort"排序 "aggs"聚合
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//2.1 设置查询方式 请求参数中 "query"
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//2.1.1 设置查询条件中关键字 小米手机->小米 and 手机
if (StringUtils.isNotBlank(searchParam.getKeyword())) {
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND);
boolQueryBuilder.must(matchQueryBuilder);
}
//2.1.2 设置查询条件中分类 可能会提交任意一级分类
if (searchParam.getCategory1Id() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
}
if (searchParam.getCategory2Id() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
}
if (searchParam.getCategory3Id() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
}
//2.1.3 设置查询条件中品牌 &trademark= 1:小米
if (StringUtils.isNotBlank(searchParam.getTrademark())) {
String[] split = searchParam.getTrademark().split(":");
if (split != null && split.length == 2) {
boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
}
}
//2.1.4 设置查询条件中平台属性 格式多个props=平台属性id:平台属性值:平台属性名称 13:6G:运行内存
//2.1.4.1 创建多平台属性bool
BoolQueryBuilder attrBoolQueryBuilder = QueryBuilders.boolQuery();
String[] props = searchParam.getProps();
if (props != null && props.length != 0) {
//2.1.4.2 创建某个平台属性条件 nested 查询 每一组nested包含一组平台属性
for (String prop : props) {
//prop 形式 13:6G:运行内存
String[] split = prop.split(":");
if (split != null && split.length == 3) {
BoolQueryBuilder atrrQueryBuilder = QueryBuilders.boolQuery();
atrrQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
atrrQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", atrrQueryBuilder, ScoreMode.None);
attrBoolQueryBuilder.must(nestedQuery);
}
}
}
boolQueryBuilder.filter(attrBoolQueryBuilder);
sourceBuilder.query(boolQueryBuilder);
//2.2 设置分页 请求参数中 "from" "size"
int from = (searchParam.getPageNo() - 1) * searchParam.getPageSize();
sourceBuilder.from(from).size(searchParam.getPageSize());
//2.3 设置高亮 请求参数中 "highlight" 高亮字段 前置标签 后置标签
//2.3.1 创建高亮对象
HighlightBuilder highlightBuilder = new HighlightBuilder();
//2.3.2 设置高亮字段
highlightBuilder.field("title");
//2.3.3 设置高亮前置标签
highlightBuilder.preTags("<font style='color:red'>");
//2.3.4 设置高亮后置标签
highlightBuilder.postTags("</font>");
sourceBuilder.highlighter(highlightBuilder);
//2.4 设置排序 请求参数中 "sort" 前端:order=排序规则:升序/降序 2:asc
String order = searchParam.getOrder();
if (StringUtils.isNotBlank(order)) {
String[] split = order.split(":");
if (split != null && split.length == 2) {
String orderField = "";
switch (split[0]) {
case "1":
orderField = "hotScore";
break;
case "2":
orderField = "price";
break;
}
sourceBuilder.sort(orderField, "asc".equals(split[1]) ? SortOrder.ASC : SortOrder.DESC);
}
} else {
//设置默认排序规则 按照热门商品降序排
sourceBuilder.sort("hotScore", SortOrder.DESC);
}
//2.5 设置过滤字段
sourceBuilder.fetchSource(new String[]{"id", "defaultImg", "title", "price"}, null);
//2.6 设置聚合 品牌聚合 平台属性聚合
//2.6.1 设置品牌聚合(对检索到所有商品进行按照品牌ID进行分组) 三要素:聚合名称;聚合字段;聚合方式
//2.6.1.1 创建品牌ID聚合对象-加入到 sourceBuilder中"aggs"中
TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg").field("tmId").size(20);
//2.6.1.2 创建嵌套品牌名称聚合对象
tmIdAgg.subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName").size(1));
//2.6.1.3 创建嵌套品牌LOGO聚合对象
tmIdAgg.subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl").size(1));
sourceBuilder.aggregation(tmIdAgg);
//2.6.2 设置平台属性聚合 注意:平台属性采用nested聚合
//2.6.2.1 创建平台属性聚合对象-加入到 sourceBuilder中"aggs"中 平台属性聚合包含 平台属性ID聚合;平台属性ID聚合包含平台属性名称以及值聚合
NestedAggregationBuilder attrsNestedAgg = AggregationBuilders.nested("attrsAgg", "attrs");
//2.6.2.2 在平台属性聚合下构建属性ID子聚合
TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(20);
//2.6.2.3 在属性ID聚合下构建属性名称子聚合
attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1));
//2.6.2.4 在属性ID聚合下构建属性值子聚合
attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(20));
attrsNestedAgg.subAggregation(attrIdAgg);
//2.7 将平台属性聚合加入到"aggs"参数中
sourceBuilder.aggregation(attrsNestedAgg);
//3.将SearchSourceBuilder加入到SearchRequest
System.err.println(sourceBuilder.toString());
return searchRequest.source(sourceBuilder);
}
/**
* 解析ES响应结果
* 主要从响应JSON中获取分页信息;业务商品列表;聚合到品牌&平台属性
*
* @param response ES响应JSON封装java对象
* @param searchParam 提交查询条件
* @return
*/
private SearchResponseVo parseResult(SearchResponse response, SearchParam searchParam) {
//1.创建响应结果对象 为给响应对象中属性赋值
SearchResponseVo responseVo = new SearchResponseVo();
//2.封装分页信息
responseVo.setPageNo(searchParam.getPageNo());
responseVo.setPageSize(searchParam.getPageSize());
//2.1 从ES返回JSON中获取总记录数
SearchHits hits = response.getHits();
long total = hits.getTotalHits().value;
//2.2 计算总页数 如果总记录数对页大小能整除取则返回取商结果 反之 不能整除取商结果+1
long totalPage = total % searchParam.getPageSize() == 0 ? (total / searchParam.getPageSize()) : (total / searchParam.getPageSize()) + 1;
responseVo.setTotal(total);
responseVo.setTotalPages(totalPage);
//3.封装响应商品数据-注意解析高亮结果
SearchHit[] subHits = hits.getHits();
if (subHits != null && subHits.length > 0) {
List<Goods> goodsList = new ArrayList<>();
for (SearchHit subHit : subHits) {
//3.1获取商品文档JSON字符串
String goodsJsonStr = subHit.getSourceAsString();
//3.2将字符串转为商品对象Goods
Goods goods = JSON.parseObject(goodsJsonStr, Goods.class);
//3.3获取高亮结果 给 标题重新赋值
if (!CollectionUtils.isEmpty(subHit.getHighlightFields())) {
Text title = subHit.getHighlightFields().get("title").getFragments()[0];
goods.setTitle(title.toString());
}
goodsList.add(goods);
}
responseVo.setGoodsList(goodsList);
}
//4. 封装聚合结果 品牌&平台属性
Map<String, Aggregation> allAggregationMap = response.getAggregations().asMap();
//4.1 处理响应结果中品牌聚合信息 封装到SearchResponseVo对象
ParsedLongTerms tmIdAgg = (ParsedLongTerms) allAggregationMap.get("tmIdAgg");
//4.2 获取品牌ID聚合结果桶
List<SearchResponseTmVo> tmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
SearchResponseTmVo tmVo = new SearchResponseTmVo();
long tmId = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();
//4.2.1 通过品牌ID聚合桶获取品牌名称桶
ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
//4.2.2 通过品牌ID聚合桶获取品牌Logo桶
ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
tmVo.setTmId(tmId);
tmVo.setTmName(tmName);
tmVo.setTmLogoUrl(tmLogoUrl);
return tmVo;
}).collect(Collectors.toList());
responseVo.setTrademarkList(tmVoList);
//5.处理响应结果中平台属性聚合信息 封装到SearchResponseVo对象
//5.1 从响应聚合结果中获取平台属性聚合
ParsedNested attrsAgg = (ParsedNested) allAggregationMap.get("attrsAgg");
//5.2 从平台属性聚合对象获取聚合桶集合(所有的分组后平台属性ID以及值)
ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();
//5.3 从上面聚合桶集合进行遍历获取平台属性ID的值--封装响应结果中平台属性集合对象
if (!CollectionUtils.isEmpty(buckets)) {
List<SearchResponseAttrVo> attrVos = buckets.stream().map(bucket -> {
SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
//获取平台属性ID
long atrrId = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();
//获取平台属性名称
//5.3.1 循环中获取平台名称聚合桶集合-数量只有一个
ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
//获取平台属性值集合
//5.3.2 循环中获取平台属性值聚合桶集合
ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
//获取平台属性桶中结果
List<String> attrValueList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
attrVo.setAttrId(atrrId);
attrVo.setAttrName(attrName);
attrVo.setAttrValueList(attrValueList);
return attrVo;
}).collect(Collectors.toList());
//为响应前端检索结果VO设置聚合得到平台属性集合
responseVo.setAttrsList(attrVos);
}
return responseVo;
}
}
说明:http://localhost:8203/doc.html,左上角下拉框选择 webApi进行测试
测试参数:
#根据分类检索
{
"category3Id": 61
}
#根据分类,关键字检索
{
"keyword":"手机"
"category3Id": 61
}
#根据分类品牌关键字检索
{
"keyword":"手机",
"category3Id": 61,
"trademark":"2:苹果"
}
#加入排序
{
"keyword":"手机",
"category3Id": 61,
"trademark":"2:苹果",
"order":"2:desc"
}
#加入排序
{
"keyword":"手机",
"category3Id": 61,
"trademark":"2:苹果",
"order":"2:desc",
"props":["23:6G:运行内存","24:128G:机身内存"]
}
在service-list-client
模块ListFeignClient
远程调用Feign接口中增加方法
package com.atguigu.gmall.list.client;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.impl.ListDegradeFeignClient;
import com.atguigu.gmall.list.model.SearchParam;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(value = "service-list", fallback = ListDegradeFeignClient.class)
public interface ListFeignClient {
//...
/**
* 搜索商品
* @param listParam
* @return
*/
@PostMapping("/api/list/inner")
Result list(@RequestBody SearchParam listParam);
/**
* 上架商品
* @param skuId
* @return
*/
@GetMapping("/api/list/inner/upperGoods/{skuId}")
Result upperGoods(@PathVariable("skuId") Long skuId);
/**
* 下架商品
* @param skuId
* @return
*/
@GetMapping("/api/list/inner/lowerGoods/{skuId}")
Result lowerGoods(@PathVariable("skuId") Long skuId);
}
远程调用接口的服务降级类
package com.atguigu.gmall.list.client.impl;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.list.model.SearchParam;
import com.atguigu.gmall.list.model.SearchResponseVo;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class ListDegradeFeignClient implements ListFeignClient {
@Override
public Result incrHotScore(Long skuId) {
return null;
}
@Override
public Result<Map> search(SearchParam searchParam) {
return null;
}
}
在web-all
模块中的pom.xml中增加依赖
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>service-list-client</artifactId>
<version>1.0</version>
</dependency>
在web-all
模块中处理前端搜索请求
package com.atguigu.gmall.all.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.list.model.SearchParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Map;
/**
* <p>
* 产品列表接口
* </p>
*
*/
@Controller
public class ListController {
@Autowired
private ListFeignClient listFeignClient;
/**
* 门户页面中商品检索页面渲染
*
* @param searchParam
* @param model
* @return
*/
@GetMapping("/list.html")
public String search(SearchParam searchParam, Model model) {
Result<Map> result = listFeignClient.list(searchParam);
model.addAllAttributes(result.getData());
return "list/index";
}
}
在 gmall-gateway
网关模块,增加web-all服务的动态路由,需要在Nacos配置列表中,对server-gateway-dev.yaml
配置进行编辑增加以下信息
#==================web-all服务==========================
- id: web-comment
uri: lb://web-all
predicates:
- Host=comment.gmall.com
列表显示
<li class="yui3-u-1-5" th:each="goods: ${goodsList}">
<div class="list-wrap">
<div class="p-img">
<a th:href="@{http://item.gmall.com/{id}.html(id=${goods.id})}" target="_blank"><img th:src="${goods.defaultImg}"/></a>
</div>
<div class="price">
<strong>
<em>¥</em>
<i th:text="${goods.price}">6088.00</i>
</strong>
</div>
<div class="attr">
<a th:href="@{http://item.gmall.com/{id}.html(id=${goods.id})}" target="_blank" th:utext="${goods.title}">Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)</a>
</div>
<div class="commit">
<i class="command">已有<span>2000</span>人评价</i>
</div>
<div class="operate">
<a href="javascript:void(0);" class="sui-btn btn-bordered btn-danger">自营</a>
<a href="javascript:void(0);" class="sui-btn btn-bordered">收藏</a>
</div>
</div>
</li>
ListController
package com.atguigu.gmall.web.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.list.model.SearchAttr;
import com.atguigu.gmall.list.model.SearchParam;
import com.atguigu.gmall.list.model.SearchResponseVo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-01-07 15:33
*/
@Controller
public class ListController {
@Autowired
private ListFeignClient listFeignClient;
/**
* 门户页面中商品检索页面渲染
*
* @param searchParam
* @param model
* @return
*/
@GetMapping("/list.html")
public String list(SearchParam searchParam, Model model) {
//1.远程调用搜索服务获取检索数据
Result<Map> result = listFeignClient.search(searchParam);
//2.给页面模板中数据模型赋值
model.addAllAttributes(result.getData());
//2.1 用户选中品牌面包屑返回,用于页面品牌面包屑展示
//2.2 用户选中搜索过滤条件-平台数据,用于页面平台属性面包屑展示 propsParamList
//2.3 用户在地址栏中请求完整URL需要拼接返回,用于页面浏览器地址栏中展示 urlParam
String urlParam = this.makeUrlParam(searchParam);
model.addAttribute("urlParam", urlParam);
//2.4 用户所有搜索条件对象SearchParam返回
model.addAttribute("searchParam", searchParam);
//2.5 根据前端选择排序-页面中需要选中排序样式
//3.返回页面模板进行渲染
return "list/index.html";
}
/**
* 根据前端提交的条件对象封装完整的URL地址
*
* @param searchParam
* @return
*/
private String makeUrlParam(SearchParam searchParam) {
//1.构建最基础的请求地址字符串 /list.html?
StringBuilder urlStringBuilder = new StringBuilder("/list.html?");
//2.处理分类参数
if (searchParam.getCategory1Id() != null) {
urlStringBuilder.append("&category1Id=").append(searchParam.getCategory1Id());
}
if (searchParam.getCategory2Id() != null) {
urlStringBuilder.append("&category2Id=").append(searchParam.getCategory2Id());
}
if (searchParam.getCategory3Id() != null) {
urlStringBuilder.append("&category3Id=").append(searchParam.getCategory3Id());
}
//3.处理品牌参数
if (StringUtils.isNotBlank(searchParam.getTrademark())) {
String[] split = searchParam.getTrademark().split(":");
if (split != null && split.length == 2) {
urlStringBuilder.append("&trademark=").append(searchParam.getTrademark());
}
}
//4.处理关键字参数
if (StringUtils.isNotBlank(searchParam.getKeyword())) {
urlStringBuilder.append("&keyword=" + searchParam.getKeyword());
}
//5.处理排序参数
if (StringUtils.isNotBlank(searchParam.getOrder())) {
urlStringBuilder.append("&order=").append(searchParam.getOrder());
}
//6.处理平台属性参数
String[] props = searchParam.getProps();
if (props != null && props.length != 0) {
for (String prop : props) {
String[] split = prop.split(":");
if (split != null && split.length == 3) {
urlStringBuilder.append("&props=").append(prop);
}
}
}
return urlStringBuilder.toString();
}
}
页面处理属性:平台属性处理 web-all/resources/templates/list/index.html
<div class="type-wrap" th:each="baseAttrInfo:${attrsList}" th:unless="${#strings.contains(urlParam, 'props='+baseAttrInfo.attrId)}">
<div class="fl key" th:text="${baseAttrInfo.attrName}">网络制式</div>
<div class="fl value">
<ul class="type-list">
<li th:each="attrValue:${baseAttrInfo.attrValueList}">
<a href="" th:text="${attrValue}" >属性值111</a>
</li>
</ul>
</div>
<div class="fl ext"></div>
</div>
说明:
1,这样平台属性就拼接到url中,并且能保持参数了
2,点击平台属性,改平台属性就不在列表中显示了,控制如下:
th:unless="${#strings.contains(urlParam, 'props='+baseAttrInfo.attrId)}"
页面处理品牌显示
说明:th:if="${searchParam.trademark == null}" 控制品牌是否显示
<div class="type-wrap logo" th:if="${searchParam.trademark == null}">
<div class="fl key brand">品牌</div>
<div class="value logos">
<ul class="logo-list">
<li th:each="trademark:${trademarkList}">
<a href="" th:text="${trademark.tmName}">属性值</a>
</li>
</ul>
</div>
</div>
说明:目前页面已经渲染,但是搜索条件我们怎么处理,搜索条件值如何保持等问题还没解决,如图:
说明:所有的搜索条件都拼接到了一个url上面,除分页参数与排序
页面处理分页
<div class="sui-pagination pagination-large">
<ul>
<li class="prev" th:if="${pageNo != 1}">
<a th:href="${urlParam}+'&pageNo='+${pageNo - 1}">上一页</a>
</li>
<li class="prev disabled" th:if="${pageNo == 1}">
<a href="javascript:">上一页</a>
</li>
<li th:each="i : ${#numbers.sequence(1,totalPages)}" th:class="${i == pageNo} ? 'active' : ''">
<a th:href="${urlParam}+'&pageNo='+${i}"><span th:text="${i}"></span></a>
</li>
<li class="next" th:if="${pageNo < totalPages}">
<a th:href="${urlParam}+'&pageNo='+${pageNo + 1}">下一页</a>
</li>
<li class="next disabled" th:if="${pageNo == totalPages}">
<a href="javascript:">下一页</a>
</li>
</ul>
<div><span>共<span th:text="${totalPages }"></span>页 </span><span></div>
</div>
品牌与平台属性
ListController 修改 search方法
package com.atguigu.gmall.web.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.list.model.SearchAttr;
import com.atguigu.gmall.list.model.SearchParam;
import com.atguigu.gmall.list.model.SearchResponseVo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-01-07 15:33
*/
@Controller
public class ListController {
@Autowired
private ListFeignClient listFeignClient;
/**
* 门户页面中商品检索页面渲染
*
* @param searchParam
* @param model
* @return
*/
@GetMapping("/list.html")
public String list(SearchParam searchParam, Model model) {
//1.远程调用搜索服务获取检索数据
Result<Map> result = listFeignClient.search(searchParam);
//2.给页面模板中数据模型赋值
model.addAllAttributes(result.getData());
//2.1 用户选中品牌面包屑返回,用于页面品牌面包屑展示
String trademarkParam = this.makeTrademark(searchParam.getTrademark());
model.addAttribute("trademarkParam", trademarkParam);
//2.2 用户选中搜索过滤条件-平台数据,用于页面平台属性面包屑展示 propsParamList
List<SearchAttr> propsParamList = this.makePropsParamList(searchParam.getProps());
model.addAttribute("propsParamList", propsParamList);
//2.3 用户在地址栏中请求完整URL需要拼接返回,用于页面浏览器地址栏中展示 urlParam
String urlParam = this.makeUrlParam(searchParam);
model.addAttribute("urlParam", urlParam);
//2.4 用户所有搜索条件对象SearchParam返回
model.addAttribute("searchParam", searchParam);
//2.5 根据前端选择排序-页面中需要选中排序样式
//3.返回页面模板进行渲染
return "list/index.html";
}
/**
* 根据用户选择平台属性,封装页面中显示平台属性面包屑
*
* @param props
* @return
*/
private List<SearchAttr> makePropsParamList(String[] props) {
if (props != null && props.length != 0) {
List<SearchAttr> searchAttrsList = new ArrayList<>();
for (String prop : props) {
SearchAttr searchAttr = new SearchAttr();
String[] split = prop.split(":");
if (split != null && split.length == 3) {
searchAttr.setAttrId(Long.parseLong(split[0]));
searchAttr.setAttrValue(split[1]);
searchAttr.setAttrName(split[2]);
searchAttrsList.add(searchAttr);
}
}
return searchAttrsList;
}
return null;
}
/**
* 在搜索页面中显示用户已选品牌面包屑
*
* @param trademark
* @return
*/
private String makeTrademark(String trademark) {
if (StringUtils.isNotBlank(trademark)) {
String[] split = trademark.split(":");
return "品牌:" + split[1];
}
return null;
}
/**
* 根据前端提交的条件对象封装完整的URL地址
*
* @param searchParam
* @return
*/
private String makeUrlParam(SearchParam searchParam) {
//1.构建最基础的请求地址字符串 /list.html?
StringBuilder urlStringBuilder = new StringBuilder("/list.html?");
//2.处理分类参数
if (searchParam.getCategory1Id() != null) {
urlStringBuilder.append("&category1Id=").append(searchParam.getCategory1Id());
}
if (searchParam.getCategory2Id() != null) {
urlStringBuilder.append("&category2Id=").append(searchParam.getCategory2Id());
}
if (searchParam.getCategory3Id() != null) {
urlStringBuilder.append("&category3Id=").append(searchParam.getCategory3Id());
}
//3.处理品牌参数
if (StringUtils.isNotBlank(searchParam.getTrademark())) {
String[] split = searchParam.getTrademark().split(":");
if (split != null && split.length == 2) {
urlStringBuilder.append("&trademark=").append(searchParam.getTrademark());
}
}
//4.处理关键字参数
if (StringUtils.isNotBlank(searchParam.getKeyword())) {
urlStringBuilder.append("&keyword=" + searchParam.getKeyword());
}
//5.处理排序参数
if (StringUtils.isNotBlank(searchParam.getOrder())) {
urlStringBuilder.append("&order=").append(searchParam.getOrder());
}
//6.处理平台属性参数
String[] props = searchParam.getProps();
if (props != null && props.length != 0) {
for (String prop : props) {
String[] split = prop.split(":");
if (split != null && split.length == 3) {
urlStringBuilder.append("&props=").append(prop);
}
}
}
return urlStringBuilder.toString();
}
}
前台页面数据展示:
页面处理
1,关键字
<ul class="fl sui-breadcrumb">
<li>
<a href="#">全部结果</a>
</li>
<li class="active">
<span th:text="${searchParam.keyword}"></span>
</li>
</ul>
2,品牌处理
<ul class="fl sui-tag">
<li th:if="${searchParam.trademark != null}" class="with-x">
<span th:text="${trademarkParam}"></span>
<a th:href="@{${#strings.replace(urlParam,'trademark='+searchParam.trademark,'')}}">×</a>
</li>
</ul>
说明:urlParam里面已经包含品牌参数,该链接必须去除该参数,所以我们可以使用thymeleaf 字符串替换函数,把品牌参数替换了就可以了,
${#strings.replace(urlParam,'trademark='+searchParam.trademark,'')}
3,平台属性处理
<ul class="fl sui-tag">
<li th:if="${searchParam.props != null}" th:each="prop : ${propsParamList}" class="with-x">
<span th:text="${prop.attrName}+':'+${prop.attrValue}"></span>
<a th:href="@{${#strings.replace(urlParam+'&order='+searchParam.order,'props='+prop.attrId+':'+prop.attrValue+':'+prop.attrName,'')}}">×</a>
</li>
</ul>
说明:与品牌一样,替换掉对应的平台属性值即可
${#strings.replace(urlParam,'props='+prop.attrId+':'+prop.attrValue+':'+prop.attrName,'')}
注意:只要做了排序回显,业务数据就可以渲染
ListController
package com.atguigu.gmall.web.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.list.model.SearchAttr;
import com.atguigu.gmall.list.model.SearchParam;
import com.atguigu.gmall.list.model.SearchResponseVo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-01-07 15:33
*/
@Controller
public class ListController {
@Autowired
private ListFeignClient listFeignClient;
/**
* 门户页面中商品检索页面渲染
*
* @param searchParam
* @param model
* @return
*/
@GetMapping("/list.html")
public String list(SearchParam searchParam, Model model) {
//1.远程调用搜索服务获取检索数据
Result<Map> result = listFeignClient.search(searchParam);
//2.给页面模板中数据模型赋值
model.addAllAttributes(result.getData());
//2.1 用户选中品牌面包屑返回,用于页面品牌面包屑展示
String trademarkParam = this.makeTrademark(searchParam.getTrademark());
model.addAttribute("trademarkParam", trademarkParam);
//2.2 用户选中搜索过滤条件-平台数据,用于页面平台属性面包屑展示 propsParamList
List<SearchAttr> propsParamList = this.makePropsParamList(searchParam.getProps());
model.addAttribute("propsParamList", propsParamList);
//2.3 用户在地址栏中请求完整URL需要拼接返回,用于页面浏览器地址栏中展示 urlParam
String urlParam = this.makeUrlParam(searchParam);
model.addAttribute("urlParam", urlParam);
//2.4 用户所有搜索条件对象SearchParam返回
model.addAttribute("searchParam", searchParam);
//2.5 根据前端选择排序-页面中需要选中排序样式
Map<String, String> orderMap = this.makeOrderMap(searchParam.getOrder());
model.addAttribute("orderMap", orderMap);
//3.返回页面模板进行渲染
return "list/index.html";
}
/**
* 根据用户选择平台属性,封装页面中显示平台属性面包屑
*
* @param props
* @return
*/
private List<SearchAttr> makePropsParamList(String[] props) {
if (props != null && props.length != 0) {
List<SearchAttr> searchAttrsList = new ArrayList<>();
for (String prop : props) {
SearchAttr searchAttr = new SearchAttr();
String[] split = prop.split(":");
if (split != null && split.length == 3) {
searchAttr.setAttrId(Long.parseLong(split[0]));
searchAttr.setAttrValue(split[1]);
searchAttr.setAttrName(split[2]);
searchAttrsList.add(searchAttr);
}
}
return searchAttrsList;
}
return null;
}
/**
* 在搜索页面中显示用户已选品牌面包屑
*
* @param trademark
* @return
*/
private String makeTrademark(String trademark) {
if (StringUtils.isNotBlank(trademark)) {
String[] split = trademark.split(":");
return "品牌:" + split[1];
}
return null;
}
/**
* 根据前端提交的条件对象封装完整的URL地址
*
* @param searchParam
* @return
*/
private String makeUrlParam(SearchParam searchParam) {
//1.构建最基础的请求地址字符串 /list.html?
StringBuilder urlStringBuilder = new StringBuilder("/list.html?");
//2.处理分类参数
if (searchParam.getCategory1Id() != null) {
urlStringBuilder.append("&category1Id=").append(searchParam.getCategory1Id());
}
if (searchParam.getCategory2Id() != null) {
urlStringBuilder.append("&category2Id=").append(searchParam.getCategory2Id());
}
if (searchParam.getCategory3Id() != null) {
urlStringBuilder.append("&category3Id=").append(searchParam.getCategory3Id());
}
//3.处理品牌参数
if (StringUtils.isNotBlank(searchParam.getTrademark())) {
String[] split = searchParam.getTrademark().split(":");
if (split != null && split.length == 2) {
urlStringBuilder.append("&trademark=").append(searchParam.getTrademark());
}
}
//4.处理关键字参数
if (StringUtils.isNotBlank(searchParam.getKeyword())) {
urlStringBuilder.append("&keyword=" + searchParam.getKeyword());
}
//5.处理排序参数
if (StringUtils.isNotBlank(searchParam.getOrder())) {
urlStringBuilder.append("&order=").append(searchParam.getOrder());
}
//6.处理平台属性参数
String[] props = searchParam.getProps();
if (props != null && props.length != 0) {
for (String prop : props) {
String[] split = prop.split(":");
if (split != null && split.length == 3) {
urlStringBuilder.append("&props=").append(prop);
}
}
}
return urlStringBuilder.toString();
}
/**
* 根据用户选择排序返回选中排序Map
*
* @param order
* @return
*/
private Map<String, String> makeOrderMap(String order) {
HashMap<String, String> orderMap = new HashMap<>();
if (StringUtils.isNotBlank(order)) {
//前端选择排序方式 参数形式:&order=2:desc
String[] split = order.split(":");
if (split != null && split.length == 2) {
orderMap.put("type", split[0]);
orderMap.put("sort", split[1]);
return orderMap;
}
} else {
//前端没有选择 ES检索默认按照热点商品降序
orderMap.put("type", "1");
orderMap.put("sort", "desc");
}
return orderMap;
}
}
页面
<ul class="sui-nav">
<li th:class="${orderMap.type == '1' ? 'active': ''}">
<a th:href="${urlParam}+'&order=1:'+${orderMap.sort == 'asc' ? 'desc' : 'asc'}">
综合<span th:if="${orderMap.type == '1'}" th:text="${orderMap.sort == 'asc' ? '↑' : '↓'}"></span>
</a>
</li>
<li th:class="${orderMap.type == '2' ? 'active': ''}">
<a th:href="${urlParam}+'&order=2:'+${orderMap.sort == 'asc' ? 'desc' : 'asc'}">
价格<span th:if="${orderMap.type == '2'}" th:text="${orderMap.sort == 'asc' ? '↑' : '↓'}"></span>
</a>
</li>
<li>
<a href="#">新品</a>
</li>
<li>
<a href="#">评价</a>
</li>
</ul>
说明:
1,排序没有拼接到urlParam中,原因:如果拼接会重复出现
2,为了保持排序条件,所以其他所有链接都需加上排序参数
改造其他连接
分页
<a th:href="${urlParam}+'&pageNo='+${i}+'&order='+${searchParam.order}"><span th:text="${i}"></span></a>
1,平台属性
<a th:href="${urlParam}+'&props='+${baseAttrInfo.attrId}+':'+${attrValue}+':'+${baseAttrInfo.attrName}+'&order='+${searchParam.order}" th:text="${attrValue}" >属性值111</a>
</li>
2,品牌
<a th:href="${urlParam}+'&trademark='+${trademark.tmId}+':'+${trademark.tmName}+'&order='+${searchParam.order}" th:text="${trademark.tmName}">属性值111</a>
</li>
3,面包屑
<ul class="fl sui-tag">
<li th:if="${searchParam.trademark != null}" class="with-x">
<span th:text="${trademarkParam}"></span>
<a th:href="@{${#strings.replace(urlParam+'&order='+searchParam.order,'trademark='+searchParam.trademark,'')}}">×</a>
</li>
<li th:if="${searchParam.props != null}" th:each="prop : ${propsParamList}" class="with-x">
<span th:text="${prop.attrName}+':'+${prop.attrValue}"></span>
<a th:href="@{${#strings.replace(urlParam+'&order='+searchParam.order,'props='+prop.attrId+':'+prop.attrValue+':'+prop.attrName,'')}}">×</a>
</li>
</ul>
看电商软件环境安装.md
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.1</version>
</dependency>
创建logback-spring.xml配置文件,内存如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextName>logback</contextName>
<!-- 日志的输出目录 -->
<property name="log.path" value="D:/logs/gmall/list" />
<!--控制台日志格式:彩色日志-->
<!-- magenta:洋红 -->
<!-- boldMagenta:粗红-->
<!-- cyan:青色 -->
<!-- white:白色 -->
<!-- magenta:洋红 -->
<property name="CONSOLE_LOG_PATTERN"
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) %highlight([%-5level]) %green(%logger) %msg%n"/>
<!--文件日志格式-->
<property name="FILE_LOG_PATTERN"
value="%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n" />
<!--编码-->
<property name="ENCODING"
value="UTF-8" />
<!-- 控制台日志 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 临界值过滤器 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<!--CONSOLE_LOG_PATTERN 控制台格式 FILE_LOG_PATTERN 文件格式-->
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>${ENCODING}</charset>
</encoder>
</appender>
<!-- 文件日志 -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!--日志输出的目录是在哪?-->
<file>${log.path}/log.log</file>
<append>true</append>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${ENCODING}</charset>
</encoder>
</appender>
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 级别过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch><!-- 当前要输出的日志如果是ERROR级别,则输出 -->
<onMismatch>DENY</onMismatch><!-- 当前要输出的日志如果不是ERROR级别,则拒绝输出 -->
</filter>
<!-- 要区别于其他的appender中的文件名字 -->
<file>${log.path}/log-rolling-error.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${ENCODING}</charset>
</encoder>
<!-- 设置滚动日志记录的滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-rolling-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--归档日志文件保留的最大数量-->
<maxHistory>15</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- logstash日志 -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- logstash ip和暴露的端口,logback就是通过这个地址把日志发送给logstash -->
<destination>192.168.200.128:5044</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
<!-- 开发环境 -->
<springProfile name="dev">
<!-- com.atguigu日志记录器:业务程序INFO,debug,warn,error级别 -->
<logger name="com.atguigu" level="INFO" />
<!-- 根日志记录器:INFO级别 -->
<root level="INFO">
<!--控制台输出模式-->
<appender-ref ref="CONSOLE" />
<!--FILE 文件基本输出-->
<!--<appender-ref ref="FILE" />-->
<!--有回滚日志记录-->
<appender-ref ref="ROLLING_FILE" />
<!--配置logstash 日志!-->
<appender-ref ref="LOGSTASH" />
</root>
</springProfile>
<!-- 生产或和试环境 -->
<!-- <springProfile name="test,prod">-->
<!-- <logger name="com.atguigu" level="INFO" additivity="false">-->
<!-- <appender-ref ref="CONSOLE" />-->
<!-- </logger>-->
<!-- <root level="ERROR">-->
<!-- <appender-ref ref="CONSOLE" />-->
<!-- <appender-ref ref="ROLLING_FILE" />-->
<!-- </root>-->
<!-- </springProfile>-->
</configuration>