代码解析
service层代码,大致做了4步骤:
- 准备Request
- DSL语句
- 发起Request请求得到响应Response
- 响应体解析(排序、高亮)
想都不用向回答我,哪一个是最重要的步骤?是的DSL语句,也就是具体的查询,那么具体的查询怎么写呢?
是的就是这么几行,一个BoolQuery搞定,有点像MyBatisPlus的QueryWrapper,只需要你会关键字(逻辑:must和、should或)(match模糊搜索,term精确搜索,range范围搜索)
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.matchQuery("all", requestParam.getKey()));
boolQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
boolQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
boolQuery.filter(QueryBuilders.termQuery("starName", requestParam.getStarName()));
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParam.getMinPrice()).lte(requestParam.getMaxPrice()));
好了会了基础下面的代码能看懂了,
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 完成关键字搜索和分页
*
* @param requestParam
* @return cn.itcast.hotel.pojo.PageResult
* @author lst
* @date 2023/11/27 17:03
*/
public PageResult getHotelList(RequestParams requestParam) throws IOException {
//1、准备Request
SearchRequest request = new SearchRequest("hotel");
//2、DSL语句
handleDSL(request, requestParam);
//3、发起Request请求得到响应Response
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
//4、响应体解析,高亮
return handleResponse(response, HotelDoc.class);
}
private void handleDSL(SearchRequest request, RequestParams requestParam) {
//根据关键字搜索
keywordSearch(request, requestParam);
//分页搜索
fenye(request, requestParam.getPage(), requestParam.getSize());
//距离排序
locationOrder(request, requestParam);
}
/**
* 关键字为空查询所有,不为空根据关键字查询
*
* @param request
* @param requestParam
* @author lst
* @date 2023/11/28 10:34
*/
private void keywordSearch(SearchRequest request, RequestParams requestParam) {
// 1.准备Boolean查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 1.1.关键字搜索,match查询,放到must中
String key = requestParam.getKey();
if (StringUtils.isNotBlank(key)) {
// 不为空,根据关键字查询
boolQuery.must(QueryBuilders.matchQuery("all", key));
} else {
// 为空,查询所有
boolQuery.must(QueryBuilders.matchAllQuery());
}
// 1.2.品牌
String brand = requestParam.getBrand();
if (StringUtils.isNotBlank(brand)) {
boolQuery.filter(QueryBuilders.termQuery("brand", brand));
}
// 1.3.城市
String city = requestParam.getCity();
if (StringUtils.isNotBlank(city)) {
boolQuery.filter(QueryBuilders.termQuery("city", city));
}
// 1.4.星级
String starName = requestParam.getStarName();
if (StringUtils.isNotBlank(starName)) {
boolQuery.filter(QueryBuilders.termQuery("starName", starName));
}
// 1.5.价格范围
Integer minPrice = requestParam.getMinPrice();
Integer maxPrice = requestParam.getMaxPrice();
if (minPrice != null && maxPrice != null) {
maxPrice = maxPrice == 0 ? Integer.MAX_VALUE : maxPrice;
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
}
// 2.算分函数查询
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery, // 原始查询,boolQuery
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAD", true), ScoreFunctionBuilders.weightFactorFunction(10))});
// 3.设置查询条件
request.source().query(functionScoreQuery);
}
private void fenye(SearchRequest request, int page, int size) {
request.source().from((page - 1) * size).size(size);
}
private void locationOrder(SearchRequest request, RequestParams requestParam) {
String location = requestParam.getLocation();
if (StringUtils.isNotBlank(location)) {
request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));
}
}
题外话
这里handleResponse方法有一个演变过程,起初我的想法是为了让其更有通用性写成泛型吧,但是考虑到泛型还是得不停的if else 判断是什么实体类强转,与其如此不如让实现某一接口的实体类都能用此方法(这个思想在原公司的分页查询中就有体现),最开始泛型代码如下所示:
private <T> PageResult handleResponse(SearchResponse response, Class<T> documentType) {
SearchHits searchHits = response.getHits();
// 4.1.总条数
long total = searchHits.getTotalHits().value;
// 4.2.获取文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
List<T> documents = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
// 4.4.获取source
String json = hit.getSourceAsString();
// 4.5.反序列化,非高亮的
T document = JSON.parseObject(json, documentType);
// 4.6.处理高亮结果
// 1)获取高亮map
Map<String, HighlightField> map = hit.getHighlightFields();
if (map != null && !map.isEmpty()) {
// 2)根据字段名,获取高亮结果
HighlightField highlightField = map.get("name");
if (highlightField != null) {
// 3)获取高亮结果字符串数组中的第1个元素
String hName = highlightField.getFragments()[0].toString();
// 4)将高亮结果设置到文档中
if (document instanceof HotelDoc) {
((HotelDoc) document).setName(hName);
}
}
}
// 4.8.排序信息
Object[] sortValues = hit.getSortValues();
if (sortValues.length > 0) {
if (document instanceof HotelDoc) {
((HotelDoc) document).setDistance(sortValues[0]);
}
}
// 4.9.放入集合
documents.add(document);
}
return new PageResult(total, documents);
}
后来我问chatgpt如果传入的documentType每增加一种,就得多一个if else 判断这样不好吧。
他告诉我是对的,为了避免使用多个 if 语句进行类型检查,你可以使用 Java 的泛型方法和接口。首先,你可以定义一个接口,表示文档类型的通用接口,然后让 HotelDoc 实现这个接口。接下来,在 handleResponse 方法中,你可以将 T 限定为实现了该接口的类型,并调用接口的方法进行处理。这样,你就可以在不同类型之间共享通用的逻辑,而无需硬编码具体的类型检查。
public interface BaseDoc {
void setName(String name);
void setDistance(Object distance);
}
package cn.itcast.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class HotelDoc implements BaseDoc{
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
private Object distance;
private Boolean isAD;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
private <T extends Document> PageResult handleResponse(SearchResponse response, Class<T> documentType) {
SearchHits searchHits = response.getHits();
// 4.1.总条数
long total = searchHits.getTotalHits().value;
// 4.2.获取文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
List<T> documents = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
// 4.4.获取source
String json = hit.getSourceAsString();
// 4.5.反序列化,非高亮的
T document = JSON.parseObject(json, documentType);
// 4.6.处理高亮结果
// 1)获取高亮map
Map<String, HighlightField> map = hit.getHighlightFields();
if (map != null && !map.isEmpty()) {
// 2)根据字段名,获取高亮结果
HighlightField highlightField = map.get("name");
if (highlightField != null) {
// 3)获取高亮结果字符串数组中的第1个元素
String hName = highlightField.getFragments()[0].toString();
// 4)将高亮结果设置到文档中
document.setName(hName);
}
}
// 4.8.排序信息
Object[] sortValues = hit.getSortValues();
if (sortValues.length > 0) {
document.setDistance(sortValues[0]);
}
// 4.9.放入集合
documents.add(document);
}
return new PageResult(total, documents);
}