酒店查询案例
约 1088 字大约 4 分钟
2025-03-08
需求背景
某酒店网站希望能够根据用户的搜索条件进行酒店的查询,并返回相关的酒店信息。并高亮显示用户搜索的关键字。
- 用户可以输入关键字搜索酒店名称、地址、商圈,显示相关的酒店信息。
- 用户可以选择价格范围查询
- 用户可以选择星级评分查询
- 用户可以选择城市查询
- 用户可以选择酒店品牌查询
- 并且可以对结果按照 价格、评分、等排序查询
文档实体类
package com.syh.hotel.model.doc;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.syh.hotel.model.entity.Hotel;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import java.io.Serializable;
/**
*
* @TableName tb_hotel
*/
@Data
@Document(indexName = "hotel")
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class HotelDoc implements Serializable {
/**
* 酒店id
*/
@Id
private Long id;
/**
* 酒店名称
*/
@Field(name = "name",type = FieldType.Text,analyzer = "ik_max_word",copyTo = "keywords")
private String name;
/**
* 酒店地址
*/
@Field(name = "address",type = FieldType.Text,analyzer = "ik_max_word",copyTo = "keywords")
private String address;
/**
* 酒店价格
*/
@Field(name = "price",type = FieldType.Integer)
private Integer price;
/**
* 酒店评分
*/
@Field(name = "score",type = FieldType.Integer)
private Integer score;
/**
* 酒店品牌
*/
@Field(name = "brand",type = FieldType.Keyword)
private String brand;
/**
* 所在城市
*/
@Field(name = "city",type = FieldType.Keyword)
private String city;
/**
* 酒店星级,1星到5星,1钻到5钻
*/
@Field(name = "starName",type = FieldType.Keyword)
private String starName;
/**
* 商圈
*/
@Field(name = "business",type = FieldType.Text,analyzer = "ik_max_word",copyTo = "keywords")
private String business;
/**
* 酒店位置
*/
@GeoPointField
private String location;
/**
* 查询关键词
*/
@Field(name = "keywords",type = FieldType.Text,analyzer = "ik_max_word")
private String keywords;
/**
* 酒店图片
*/
@Field(name = "pic", type = FieldType.Keyword)
private String pic;
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();
}
}
查询实体类
@Data
public class BaseQuery {
private int pageNum;
private int pageSize;
}
@Data
public class HotelQuery extends BasePageQuery {
private String keywords;// 关键字搜索
private List<String> brands; // 品牌搜索
private List<String> city;//城市搜索
private List<String> starName; // 星级搜索
private Integer minPrice;
private Integer maxPrice;
private String orderColum; // 排序的列
}
@Data
public class HotelGroup {
//城市分组
private List<String> cityGroup;
//品牌分组
private List<String> brandGroup;
//星级分组
private List<String> starGroup;
}
业务接口
- 统计查询酒店的城市、品牌、星级分组
@Service
@RequiredArgsConstructor
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel>
implements HotelService{
private final ElasticsearchClient elasticsearchClient;
@Override
public HotelGroup searchHotelGroup() throws IOException {
HotelGroup hotelGroup = new HotelGroup();
//聚合统计城市,品牌,星级
// GET /hotel/_search
// {
// "size": 0,
// "aggs": {
// "brand_aggs": {
// "terms": {
// "field": "brand"
// }
// },
// "city_aggs": {
// "terms": {
// "field": "city"
// }
// },
// "start_aggs": {
// "terms": {
// "field": "star"
// }
// }
// }
// }
SearchResponse<Void> search = elasticsearchClient.search(
req -> req.index("hotel")
.size(0)
.aggregations(
"brand_aggs",
a -> a.terms(a2 -> a2.field("brand"))
)
.aggregations(
"city_aggs",
a -> a.terms(a2 -> a2.field("city"))
)
.aggregations(
"star_aggs",
a -> a.terms(a2 -> a2.field("starName"))
)
, Void.class);
List<StringTermsBucket> brandAggs = search.aggregations().get("brand_aggs").sterms().buckets().array();
List<String> brandList = new ArrayList<>();
for(StringTermsBucket bucket : brandAggs){
brandList.add(bucket.key().stringValue());
}
hotelGroup.setBrandGroup(brandList);
List<StringTermsBucket> cityAggs = search.aggregations().get("city_aggs").sterms().buckets().array();
List<String> cityList = new ArrayList<>();
for(StringTermsBucket bucket : cityAggs){
cityList.add(bucket.key().stringValue());
}
hotelGroup.setCityGroup(cityList);
List<StringTermsBucket> starAggs = search.aggregations().get("star_aggs").sterms().buckets().array();
List<String> starList = new ArrayList<>();
for(StringTermsBucket bucket : starAggs){
starList.add(bucket.key().stringValue());
}
hotelGroup.setStarGroup(starList);
return hotelGroup;
}
}
- 酒店查询接口
boolean isAsc = true;
@Override
public Page<HotelDoc> searchHotelList(HotelQuery hotelQuery) throws IOException {
// 起始行
Integer startRow = (hotelQuery.getPageNum()-1)*hotelQuery.getPageSize();
// 每页显示条数
Integer pageSize = hotelQuery.getPageSize();
List<Query> queries = new ArrayList<>();
// 关键字搜索
if(StrUtil.isNotEmpty(hotelQuery.getKeywords())){
queries.add(Query.of(q->q.match(m->m.field("keywords").query(hotelQuery.getKeywords()))));
}
// 品牌搜索
if(hotelQuery.getBrands()!=null && hotelQuery.getBrands().size()>0){
queries.add(Query.of(
q->q.terms(t->t.field("brand").terms(f->f.value(
hotelQuery.getBrands().stream().map(FieldValue::of).collect(Collectors.toList())
)))
));
}
// 城市搜索
if(hotelQuery.getCity()!=null && hotelQuery.getCity().size()>0){
queries.add(Query.of(
q->q.terms(t->t.field("city").terms(f->f.value(
hotelQuery.getCity().stream().map(FieldValue::of).collect(Collectors.toList())
)))
));
}
// 星级搜索
if(hotelQuery.getStarName()!=null && hotelQuery.getStarName().size()>0){
queries.add(Query.of(
q->q.terms(t->t.field("starName").terms(f->f.value(
hotelQuery.getStarName().stream().map(FieldValue::of).collect(Collectors.toList())
)))
));
}
// 价格区间搜索
if (hotelQuery.getMinPrice() != null){
queries.add(Query.of(
q->q.range(r->r.field("price").gte(JsonData.of(hotelQuery.getMinPrice())))
));
}
if(hotelQuery.getMaxPrice() != null){
queries.add(Query.of(
q->q.range(r->r.field("price").lte(JsonData.of(hotelQuery.getMaxPrice())))
));
}
// 排序
List<SortOptions> sortOptions = new ArrayList<>();
if(hotelQuery.getOrderColum() != null && hotelQuery.getOrderColum() != "" ){
sortOptions.add(SortOptions.of(
s->s.field(f->f.field(hotelQuery.getOrderColum()).order(isAsc ? SortOrder.Asc : SortOrder.Desc))
));
isAsc = !isAsc;
}
// 高亮
// HighlighterType.Unified 统一高亮,其他类型参考HighlighterType
// Map<String, HighlightField> map = new HashMap<>();
// map.put("title", HighlightField.of(hf -> hf.numberOfFragments(0)));
// map.put("description", HighlightField.of(hf -> hf.numberOfFragments(4).fragmentSize(50)));
Map<String, HighlightField> highlightFields = new HashMap<>();
highlightFields.put("name", HighlightField.of(hf -> hf.numberOfFragments(10)));
highlightFields.put("address", HighlightField.of(hf -> hf.numberOfFragments(10)));
Highlight highlight = Highlight.of(
h -> h.type(HighlighterType.Unified)
.fields(highlightFields)
.fragmentSize(50)
.numberOfFragments(5)
.requireFieldMatch(false)
);
SearchResponse<HotelDoc> search = elasticsearchClient.search(
req -> req.index("hotel")
.from(startRow)
.size(pageSize)
.sort(sortOptions)
.query(q->q.bool(
b->b.must(queries)
))
.highlight(highlight)
, HotelDoc.class
);
Long count = search.hits().total().value();// 总记录数
List<Hit<HotelDoc>> hits = search.hits().hits();
List<HotelDoc> docs = new ArrayList<>();
for (Hit<HotelDoc> hit : hits) {
HotelDoc source = hit.source();
System.out.println(hit.highlight());
source.setName(hit.highlight().get("name")==null?source.getName():hit.highlight().get("name").get(0));
source.setAddress(hit.highlight().get("address")==null?source.getAddress():hit.highlight().get("address").get(0));
// hit.highlight();
docs.add(source);
}
return new PageImpl<>(docs, Pageable.unpaged(),count); // 封装Page对象
}