需求:根据文字搜索,也可以选择标签搜索
思路:用bool查询,先根据关键词查询全部,再根据标签过滤。
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
@Override
public PageResult search(RequestParams params) throws IOException {
SearchRequest request = new SearchRequest("hotel");
// 关键字搜索
QueryBuilder query = buildBasicQuery(params);
request.source().query(query);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return extracted(response);
}
private static QueryBuilder buildBasicQuery(RequestParams params) {
String key = params.getKey();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// query
// 关键字搜索
if ("".equals(key) || key==null){
boolQuery.must(QueryBuilders.matchAllQuery());
}else {
boolQuery.must(QueryBuilders.matchQuery("all",key));
}
// 过滤条件
if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
}
if (!("".equals(params.getCity()) || params.getCity()==null)){
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
if (!("".equals(params.getBrand()) || params.getBrand()==null)){
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
if (!("".equals(params.getStarName()) || params.getStarName()==null)){
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
return boolQuery;
}
private static PageResult extracted(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> list = Arrays.stream(hits).map(item -> {
String jsonStr = item.getSourceAsString();
Object[] sortValues = item.getSortValues();
HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, list);
}
}
需求:实现分页排序
思路:分页跟排序是单独的功能,可以根据选项排好序再分页
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
private static Boolean isLocation = false;
@Override
public PageResult search(RequestParams params) throws IOException {
SearchRequest request = new SearchRequest("hotel");
// 关键字搜索
QueryBuilder query = buildBasicQuery(params);
request.source().query(query);
// 分页
int page = params.getPage();
int size = params.getSize();
request.source().from((page-1)*size).size(size);
// 排序
if (!("default".equals(params.getSortBy()))){
FieldSortBuilder sortBy = SortBuilders.fieldSort(params.getSortBy());
if ("price".equals(params.getSortBy())){
sortBy.order(SortOrder.ASC);
}else {
sortBy.order(SortOrder.DESC);
}
request.source().sort(sortBy);
}
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return extracted(response);
}
private static QueryBuilder buildBasicQuery(RequestParams params) {
String key = params.getKey();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// query
// 关键字搜索
if ("".equals(key) || key==null){
boolQuery.must(QueryBuilders.matchAllQuery());
}else {
boolQuery.must(QueryBuilders.matchQuery("all",key));
}
// 过滤条件
if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
}
if (!("".equals(params.getCity()) || params.getCity()==null)){
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
if (!("".equals(params.getBrand()) || params.getBrand()==null)){
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
if (!("".equals(params.getStarName()) || params.getStarName()==null)){
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
return boolQuery;
}
private static PageResult extracted(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> list = Arrays.stream(hits).map(item -> {
String jsonStr = item.getSourceAsString();
Object[] sortValues = item.getSortValues();
HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, list);
}
}
要求:点击获取位置后,根据距离显示酒店,且要显示距离
思路:先判断有没有点击地图,如果携带了位置的请求就代表点击了地图,就需要根据坐标查询,且规定坐标先查找,可以保证在sort里value[0]就是坐标值
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
private static Boolean isLocation = false;
@Override
public PageResult search(RequestParams params) throws IOException {
SearchRequest request = new SearchRequest("hotel");
// 关键字搜索
QueryBuilder query = buildBasicQuery(params);
request.source().query(query);
// 分页
int page = params.getPage();
int size = params.getSize();
request.source().from((page-1)*size).size(size);
// 排序
String location = params.getLocation();
// 坐标排序
if (!("".equals(location) || location==null)){
GeoDistanceSortBuilder locationSort = SortBuilders.geoDistanceSort("location",new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS);
request.source().sort(locationSort);
FieldSortBuilder priceSort = SortBuilders.fieldSort("price").order(SortOrder.ASC);
request.source().sort(priceSort);
isLocation =true;
}
// price/defult/socre排序
if (!("default".equals(params.getSortBy()))){
FieldSortBuilder sortBy = SortBuilders.fieldSort(params.getSortBy());
if ("price".equals(params.getSortBy())){
sortBy.order(SortOrder.ASC);
}else {
sortBy.order(SortOrder.DESC);
}
request.source().sort(sortBy);
}
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return extracted(response);
}
private static QueryBuilder buildBasicQuery(RequestParams params) {
String key = params.getKey();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// query
// 关键字搜索
if ("".equals(key) || key==null){
boolQuery.must(QueryBuilders.matchAllQuery());
}else {
boolQuery.must(QueryBuilders.matchQuery("all",key));
}
// 过滤条件
if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
}
if (!("".equals(params.getCity()) || params.getCity()==null)){
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
if (!("".equals(params.getBrand()) || params.getBrand()==null)){
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
if (!("".equals(params.getStarName()) || params.getStarName()==null)){
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
return boolQuery;
}
private static PageResult extracted(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> list = Arrays.stream(hits).map(item -> {
String jsonStr = item.getSourceAsString();
Object[] sortValues = item.getSortValues();
HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
if (sortValues.length>0 && isLocation){
Object sortValue = sortValues[0];
hotelDoc.setDistance(sortValue);
}
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, list);
}
}
要求:广告置顶,有些广告需要在展示搜索结果的时候排在前面
思路:利用functionScore来做,但是functionScore是用不到bool的只能用普通的查询,在文档中维护一个字段叫isAD,在functions中过滤出isAD的文档,重新分配权重
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
private static Boolean isLocation = false;
@Override
public PageResult search(RequestParams params) throws IOException {
SearchRequest request = new SearchRequest("hotel");
// 关键字搜索
QueryBuilder query = buildBasicQuery(params);
request.source().query(query);
// 分页
int page = params.getPage();
int size = params.getSize();
request.source().from((page-1)*size).size(size);
// 排序
String location = params.getLocation();
if (!("".equals(location) || location==null)){
GeoDistanceSortBuilder locationSort = SortBuilders.geoDistanceSort("location",new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS);
request.source().sort(locationSort);
FieldSortBuilder priceSort = SortBuilders.fieldSort("price").order(SortOrder.ASC);
request.source().sort(priceSort);
isLocation =true;
}
if (!("default".equals(params.getSortBy()))){
FieldSortBuilder sortBy = SortBuilders.fieldSort(params.getSortBy());
if ("price".equals(params.getSortBy())){
sortBy.order(SortOrder.ASC);
}else {
sortBy.order(SortOrder.DESC);
}
request.source().sort(sortBy);
}
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return extracted(response);
}
private static QueryBuilder buildBasicQuery(RequestParams params) {
String key = params.getKey();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// query
// 关键字搜索
if ("".equals(key) || key==null){
boolQuery.must(QueryBuilders.matchAllQuery());
}else {
boolQuery.must(QueryBuilders.matchQuery("all",key));
}
// 过滤条件
if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
}
if (!("".equals(params.getCity()) || params.getCity()==null)){
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
if (!("".equals(params.getBrand()) || params.getBrand()==null)){
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
if (!("".equals(params.getStarName()) || params.getStarName()==null)){
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
// 广告置顶
return QueryBuilders.functionScoreQuery(boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("isAD",true),
ScoreFunctionBuilders.weightFactorFunction(10)
)
}).boostMode(CombineFunction.MULTIPLY);
}
private static PageResult extracted(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> list = Arrays.stream(hits).map(item -> {
String jsonStr = item.getSourceAsString();
Object[] sortValues = item.getSortValues();
HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
if (sortValues.length>0 && isLocation){
Object sortValue = sortValues[0];
hotelDoc.setDistance(sortValue);
}
return hotelDoc;
}).collect(Collectors.toList());
return new PageResult(total, list);
}
}
需求:从后台文档中获取实际数据,传入前端页面显示,且会动态变化,比如:选择了上海,就展示在上海的品牌,而不在上海的品牌就不显示
思路:先根据用户条件搜索文档,按照类别聚合
@Override
public Map<String, List<String>> filters(RequestParams requestParams){
Map<String, List<String>> listMap = null;
try {
SearchRequest request = new SearchRequest("hotel");
// 查询条件构建
searchAggs(request,requestParams);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
Aggregations aggregations = response.getAggregations();
listMap = new HashMap<>();
listMap.put("city",getList(aggregations, "getCity"));
listMap.put("brand",getList(aggregations, "getBrand"));
listMap.put("starName",getList(aggregations, "getStarName"));
} catch (IOException e) {
throw new RuntimeException(e);
}
return listMap;
}
private void searchAggs(SearchRequest request,RequestParams requestParams) {
QueryBuilder queryBuilder = buildBasicQuery(requestParams);
request.source().query(queryBuilder);
// 查询品牌
request.source().aggregation(
AggregationBuilders
.terms("getBrand")
.field("brand")
.size(20));
// 查询城市
request.source().aggregation(
AggregationBuilders
.terms("getCity")
.field("city")
.size(20));
// 查询星级
request.source().aggregation(
AggregationBuilders
.terms("getStarName")
.field("starName")
.size(20));
}
private List<String> getList(Aggregations aggregations, String name) {
Terms getBrand = aggregations.get(name);
List<String> list = new ArrayList<>();
for (Terms.Bucket bucket : getBrand.getBuckets()) {
String key = bucket.getKeyAsString();
list.add(key);
}
return list;
}
private QueryBuilder buildBasicQuery(RequestParams params) {
String key = params.getKey();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// query
// 关键字搜索
if ("".equals(key) || key==null){
boolQuery.must(QueryBuilders.matchAllQuery());
}else {
boolQuery.must(QueryBuilders.matchQuery("all",key));
}
// 过滤条件
if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
}
if (!("".equals(params.getCity()) || params.getCity()==null)){
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
if (!("".equals(params.getBrand()) || params.getBrand()==null)){
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
if (!("".equals(params.getStarName()) || params.getStarName()==null)){
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
// 广告置顶
return QueryBuilders.functionScoreQuery(boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("isAD",true),
ScoreFunctionBuilders.weightFactorFunction(10)
)
}).boostMode(CombineFunction.MULTIPLY);
}
需求:要求用户输入拼音,可以根据输入的拼音自动补全,例如:输入s自动补全 三,四,蛇
思路:利用分词器,在新增文档的时候,根据拼音分词器把品牌,地址做拆分,用户输入受字母的时候,后台根据首字符检索字段的拼音,要设计好拼音分词器的拆分条件
/**
* 自动补全
* @param key 补全前缀
* @return 补全数组
*/
@Override
public List<String> suggestion(String key) {
List<String> collect;
try {
SearchRequest request = new SearchRequest("hotel");
request.source().suggest(new SuggestBuilder()
.addSuggestion("mySuggestion",
SuggestBuilders
.completionSuggestion("suggestion")
.prefix(key)
.skipDuplicates(true)
.size(10)));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
CompletionSuggestion mySuggestion = response.getSuggest().getSuggestion("mySuggestion");
List<CompletionSuggestion.Entry.Option> options = mySuggestion.getOptions();
collect = options.stream().map(item -> item.getText().string()).collect(Collectors.toList());
System.err.println(collect);
} catch (IOException e) {
throw new RuntimeException(e);
}
return collect;
}
构建索引库比较关键,这里把用不到的字段省略了,不然太冗余了
#hotel
PUT /hotel
{
"mappings":{
"properties":{
"address":{
"type": "keyword",
"copy_to": "all"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"name":{
"type": "text",
"analyzer": "text_analyzere",
"search_analyzer": "ik_smart",
"copy_to": "all"
},
# all字段,用户确认搜索的时候用到的。analyzer代表新增文档的时候,按照text_analyzere这个分词器,根据最大粒度拆分,且用到了py拼音分词器。
# search_analyzer,用户确认搜索的时候用这个分词器去拆分用户的输入信息
"all":{
"type": "text",
"analyzer": "text_analyzere",
"search_analyzer": "ik_max_word"
},
#suggestion专门为自动补全做的字段,类型只能是completion,completion是一个数组,数组内的字段只能是keyword不能拆分,因为如果拆分的话就有很多个值出来,keyword保证不可拆分,那么在同一个文档内这个suggestion是固定的,比如completion由品牌跟地址组成,品牌=如家,地址=北京,那这个分词器拆出来只能是:rujia/rj,beijing/bj,
"suggestion":{
"type": "completion",
"analyzer": "completion_analyzere"
}
}
},
"settings": {
"analysis": {
"analyzer": {
#text的分词器,新增文档的时候有些text字段可能搜索的时候需要用到。
"text_analyzere":{
"tokenizer":"ik_max_word",
"filter":"py"
},
#completion的分词器
"completion_analyzere":{
"tokenizer":"keyword",
"filter":"py"
}
},
"filter": {
"type": "pinyin", //类型
"keep_full_pinyin": false,//当启用这个选项,如: 刘德华 >[ liu , de , hua ),默认值:真的
"keep_joined_full_pinyin": true,//当启用此选项时,例如: 刘德华 >[ liudehua ],默认:false
"keep_original": true,//当启用此选项时,将保留原始输入,默认值:false
"limit_first_letter_length": 16,//set first_letter结果的最大长度,默认值:16
"remove_duplicated_term": true,//当此选项启用时,重复项将被删除以保存索引,例如: de的 > de ,默认:false,注:职位相关查询可能会受到影响
"none_chinese_pinyin_tokenize" :false //非中国字母分解成单独的拼音词如果拼音,默认值:true,如:liu , de , hua , a , li , ba , ba , 13 , zhuang , han ,注意: keep_none_chinese 和 keep_none_chinese_together 应该启用
}
}
}
}
利用mq实现数据同步
生产者
@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
Long i = 1L;
hotel.setId(i);
hotelService.save(hotel);
String exchangeName = "topic.hotel";
String key = "hotel.insert";
rabbitTemplate.convertAndSend(exchangeName,key,hotel.getId());
System.err.println("发送成功--->新增");
}
消费者
@Bean
public Queue queueInsert(){
return new Queue("topic.insert.queue",true);
}
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topic.hotel");
}
@Bean
public Queue queueDelete(){
return new Queue("topic.delete.queue",true);
}
@Bean
public Binding bindingTopicBuilder(TopicExchange topicExchange,Queue queueInsert){
return BindingBuilder.bind(queueInsert).to(topicExchange).with("hotel.insert");
}
@Bean
public Binding bindingTopicBuilder2(TopicExchange topicExchange,Queue queueDelete){
return BindingBuilder.bind(queueDelete).to(topicExchange).with("hotel.delete");
}
@Component
public class ListenMq {
@Autowired
private IHotelService hotelService;
@RabbitListener(queues = "topic.insert.queue")
public void listenInsert(Long id){
hotelService.updateById(id);
}
@RabbitListener(queues = "topic.delete.queue")
public void listenDelete(Long id){
hotelService.deleteById(id);
}
}
@Override
public void updateById(Long id) {
try {
IndexRequest request = new IndexRequest("hotel").id(id.toString());
Hotel hotel = this.getById(id);
HotelDoc hotelDoc = new HotelDoc(hotel);
String jsonString = JSON.toJSONString(hotelDoc);
request.source(jsonString, XContentType.JSON);
client.index(request, RequestOptions.DEFAULT);
System.err.println("新增一条数据");
} catch (IOException e) {
throw new RuntimeException(e);
}
}