一、項目分析
啟動hotel-demo項目,訪問localhost:servicePort,即可訪問static下的index.html:
從頁面分析,我們需要實現(xiàn)搜索、分頁、排序等功能。點擊頁面,可以看到list接口的傳參為:
二、需求1:酒店搜索功能
接下來實現(xiàn)酒店搜索功能,完成關鍵字搜索和分頁。
- 定義接參的Dto類
@Data
public class RequestParam {
private String key;
private Integer page; //pageNum
private Integer size; //pageSize
private String sortBy;
}
- 定義返回的結果類
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PageResult {
private Long total;
private List<HotelDoc> hotelDocList;
}
- 定義controller接口,接收頁面請求
@RestController
@RequestMapping("/hotel")
public class HotelSearchController {
@Resource
IHotelService hotelService;
@PostMapping("/list")
public PageResult searchHotel(@RequestBody RequestParam requestParam){
return hotelService.search(requestParam);
}
}
- Service層要用到JavaRestHighLevelClient對象,在啟動類中定義這個Bean
@SpringBootApplication
public class HotelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HotelDemoApplication.class, args);
}
@Bean
public RestHighLevelClient client(){
return new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://10.4.130.220:9200")
));
}
}
- 完成Service層,利用match查詢實現(xiàn)根據(jù)關鍵字搜索酒店信息
public interface IHotelService extends IService<Hotel> {
PageResult search(RequestParam requestParam);
}
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Resource
RestHighLevelClient client; //注入客戶端操作的bean
@Override
public PageResult search(RequestParam requestParam) {
try {
SearchRequest request = new SearchRequest("hotel");
//搜索關鍵字
String key = requestParam.getKey();
if (StringUtils.isNotEmpty(key)) { //有key就走全文檢索
request.source().query(QueryBuilders.matchQuery("all", key));
} else { //沒key就走查所有
request.source().query(QueryBuilders.matchAllQuery());
}
//分頁
request.source().from((requestParam.getPage() - 1) * requestParam.getSize()) //(pageNum-1)*pageSize
.size(requestParam.getSize());
//發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//處理響應結果
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException();
}
}
//處理響應結果的方法
private PageResult handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
//Stream流將hits中的每條數(shù)據(jù)都轉為HotelDoc對象
List<HotelDoc> hotelDocList = Arrays.stream(hits).map(t -> {
String json = t.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, hotelDocList);
}
}
重啟服務,搜索和分頁已實現(xiàn)。
三、需求2:添加過濾功能
接下來添加品牌、城市、星級、價格的過濾功能。這里參與搜索的條件對應著不同的搜索類型,有全文檢索,有精確查找,自然要用復合查詢Boolean Search
- 修改接參dto類:
@Data
public class RequestParam {
private String key;
private Integer page; //pageNum
private Integer size; //pageSize
private String sortBy;
private String brand;
private String starName;
private String city;
private Integer minPrice;
private Integer maxPrice;
}
- 修改Service層實現(xiàn),這里把搜索條件的構建單獨抽取成方法,一來方便后面復用,二來讓代碼看著清爽點
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Resource
RestHighLevelClient client;
@Override
public PageResult search(RequestParam requestParam) {
try {
//準備request
SearchRequest request = new SearchRequest("hotel");
//構建查詢條件
buildBasicQuery(requestParam, request);
//分頁
request.source().from((requestParam.getPage() - 1) * requestParam.getSize())
.size(requestParam.getSize());
//發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//處理響應結果
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException();
}
}
private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {
BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
//關鍵字
String key = requestParam.getKey();
if (! isEmpty(key)) {
booleanQuery.must(QueryBuilders.matchQuery("all", key));
} else {
booleanQuery.must(QueryBuilders.matchAllQuery());
}
//城市
if (! isEmpty(requestParam.getCity())) {
booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
}
//品牌
if (! isEmpty(requestParam.getBrand())) {
booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
}
//星級
if (! isEmpty(requestParam.getStarName())) {
booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));
}
//價格
if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {
booleanQuery.filter(QueryBuilders.rangeQuery("price")
.lte(requestParam.getMaxPrice())
.gte(requestParam.getMinPrice()));
}
request.source().query(booleanQuery);
}
private static boolean isEmpty(String str){
return str == null || "".equals(str);
}
}
四、需求3:我附近的酒店
前端頁面點擊定位后,會將你所在的位置發(fā)送到后臺:
接下來實現(xiàn)根據(jù)這個坐標,將酒店結果按照到這個點的距離升序排序。
距離排序與普通字段排序有所差異,對比如下:
開始實現(xiàn)需求:
- 修改RequestParams參數(shù),接收location字段
@Data
public class RequestParam {
private String key;
private Integer page; //pageNum
private Integer size; //pageSize
private String sortBy;
private String brand;
private String starName;
private String city;
private Integer minPrice;
private Integer maxPrice;
private String location; //經(jīng)緯度位置
}
- 修改Service中,在分頁前加排序邏輯
@Override
public PageResult search(RequestParam requestParam) {
try {
//準備request
SearchRequest request = new SearchRequest("hotel");
//構建查詢條件
buildBasicQuery(requestParam, request);
//排序
String myLocation = requestParam.getLocation();
if(! isEmpty(myLocation)){
request.source().sort(SortBuilders
.geoDistanceSort("location",new GeoPoint(myLocation))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS));
}
//分頁
request.source().from((requestParam.getPage() - 1) * requestParam.getSize())
.size(requestParam.getSize());
//發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//處理響應結果
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException();
}
}
但此時發(fā)現(xiàn)返回結果中少了距離你xxx千米的信息:
查看DSL返回結果,看到距離是在sort字段中:
因此需要修改結果處理的方法,且最后pageResult中是HotelDoc對象的集合,因此,修改Hoteldoc類,加distance距離字段:
@Data
@NoArgsConstructor
public class HotelDoc {
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; //新加字段
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 PageResult handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> hotelDocList = Arrays.stream(hits).map(t -> {
String json = t.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//開始加入距離
Object[] sortValues = t.getSortValues(); //排序字段可能不止一個
if(sortValues.length > 0 ){
Object sortValue = sortValues[0];
hotelDoc.setDistance(sortValue); 拿到sort值賦值給距離
}
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, hotelDocList);
}
到此,需求實現(xiàn):
五、需求4:置頂花廣告費的酒店
實現(xiàn)讓指定的酒店在搜索結果中排名置頂:
實現(xiàn)思路為:
- HotelDoc類添加標記字段isAD,Boolean類型
- 對于出廣告費的酒店,isAD為true,前端可用這個字段給酒店打廣告標簽
- 使用function score給花錢的酒店人為增加權重,干涉排序
代碼實現(xiàn):
- hotelDoc類:
@Data
@NoArgsConstructor
public class HotelDoc {
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;
- 更新ES數(shù)據(jù),模擬某酒店出廣告費
- 加入function score算分認為控制,給isAD為true的加權
private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {
//BoolQuery原始查詢條件,原始算分
BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
//關鍵字
String key = requestParam.getKey();
if (!isEmpty(key)) {
booleanQuery.must(QueryBuilders.matchQuery("all", key));
} else {
booleanQuery.must(QueryBuilders.matchAllQuery());
}
//城市
if (!isEmpty(requestParam.getCity())) {
booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
}
//品牌
if (!isEmpty(requestParam.getBrand())) {
booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
}
//星級
if (!isEmpty(requestParam.getStarName())) {
booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));
}
//價格
if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {
booleanQuery.filter(QueryBuilders.rangeQuery("price")
.lte(requestParam.getMaxPrice())
.gte(requestParam.getMinPrice()));
}
//function score算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
booleanQuery, //第一個參數(shù)傳入booleanQuery為原始查詢,對應原始的相關性算分
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ //第二個形參,function score數(shù)組,里面有個function score元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder( //function score元素對象,第一個參數(shù)傳入篩選字段
QueryBuilders.termQuery("isAD", true), //不再用酒店品牌篩選,而是isAD字段
ScoreFunctionBuilders.weightFactorFunction(10) //算分函數(shù),用默認的乘法,權重為10
)
});
request.source().query(functionScoreQuery);
}
實現(xiàn)效果;
Function Score查詢可以控制文檔的相關性算分,使用方式如下:
最后貼上以上四個需求Service層代碼:
import cn.itcast.hotel.domain.dto.RequestParam;
import cn.itcast.hotel.domain.pojo.HotelDoc;
import cn.itcast.hotel.domain.vo.PageResult;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.domain.pojo.Hotel;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Resource
RestHighLevelClient client;
@Override
public PageResult search(RequestParam requestParam) {
try {
//準備request
SearchRequest request = new SearchRequest("hotel");
//構建查詢條件
buildBasicQuery(requestParam, request);
//排序
String myLocation = requestParam.getLocation();
if (!isEmpty(myLocation)) {
request.source().sort(SortBuilders
.geoDistanceSort("location", new GeoPoint(myLocation))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS));
}
//分頁
request.source().from((requestParam.getPage() - 1) * requestParam.getSize())
.size(requestParam.getSize());
//發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//處理響應結果
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException();
}
}
private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {
//BoolQuery原始查詢條件,原始算分
BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
//關鍵字
String key = requestParam.getKey();
if (!isEmpty(key)) {
booleanQuery.must(QueryBuilders.matchQuery("all", key));
} else {
booleanQuery.must(QueryBuilders.matchAllQuery());
}
//城市
if (!isEmpty(requestParam.getCity())) {
booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
}
//品牌
if (!isEmpty(requestParam.getBrand())) {
booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
}
//星級
if (!isEmpty(requestParam.getStarName())) {
booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));
}
//價格
if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {
booleanQuery.filter(QueryBuilders.rangeQuery("price")
.lte(requestParam.getMaxPrice())
.gte(requestParam.getMinPrice()));
}
//function score算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
booleanQuery, //第一個參數(shù)傳入booleanQuery為原始查詢,對應原始的相關性算分
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ //第二個形參,function score數(shù)組,里面有個function score元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder( //function score元素對象,第一個參數(shù)傳入篩選字段
QueryBuilders.termQuery("isAD", true), //不再用酒店品牌篩選,而是isAD字段
ScoreFunctionBuilders.weightFactorFunction(10) //算分函數(shù),用默認的乘法,權重為10
)
});
request.source().query(functionScoreQuery);
}
private PageResult handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> hotelDocList = Arrays.stream(hits).map(t -> {
String json = t.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//開始加入距離
Object[] sortValues = t.getSortValues();
if (sortValues.length > 0) {
Object sortValue = sortValues[0];
hotelDoc.setDistance(sortValue);
}
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, hotelDocList);
}
private static boolean isEmpty(String str) {
return str == null || "".equals(str);
}
}
最后,頁面上其他地方的需求實現(xiàn)思路:
排序:
前端會傳遞sortBy參數(shù),就是排序方式,后端需要判斷sortBy值是什么:文章來源:http://www.zghlxwxcb.cn/news/detail-625478.html
- default:相關度算分排序,這個不用管,es的默認排序策略
- score:根據(jù)酒店的score字段排序,也就是用戶評價,降序
- price:根據(jù)酒店的price字段排序,就是價格,升序
高亮:
文章來源地址http://www.zghlxwxcb.cn/news/detail-625478.html
request.source()
.query(QueryBuilders.matchQuery("all",requestParam.getKey()))
.highlighter(new HighlightBuilder().field("name")
.requireFieldMatch(false)
.preTags("<strong>")
.postTags("</strong")
);
到了這里,關于【ElasticSearch】ES案例:旅游酒店搜索的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!