学完linux,mybatis-plus、docker、mq、ElasticSearch后可进行黑马旅游网的学习,本小项目主要是为了对ElasticSearch、mq应用的练习
一:数据初始化
- 导入数据:
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
`address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
`price` int(10) NOT NULL COMMENT '酒店价格;例:329',
`score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
`brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
`city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
`star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
`business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
`latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
`longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
项目导入
-
修改数据库链接数据
我这里的mysql是8.0.17版本
-
创建映射
PUT /hotel
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
几个特殊字段说明:
- location:地理坐标,里面包含精度、纬度
- geo_point:有纬度(latitude)和经度(longitude)确定一个点。如:“32.8723,120.2981”
- all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索
- copy_to:将此字段数据copy到一个目标字段中
- 一个搜索条件,查询多个字段。
- 自定义一个字段:all
- all字段的取值 = name + city + brand
二:数据初始化
请求地址:http://localhost:8089/hotel/init
请求方式:get
请求参数:无
返回内容:批量查询mysql数据,保存到es服务器中
相应数据:导入的数据条数
后端代码展示
web层:
@RestController
@RequestMapping("/hotel")
public class HotelController {
@Autowired
private HotelService hotelService;
@GetMapping("/init")
public Integer init() {
return hotelService.init();
}
}
serviceimpl层:
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
public Integer init() throws IOException {
List<Hotel> hotelList = hotelMapper.selectList(null);
BulkRequest request = new BulkRequest();
for (Hotel hotel : hotelList) {
IndexRequest indexRequest = new IndexRequest("hotel");
indexRequest.id(hotel.getId().toString());
indexRequest.source(JSON.toJSONString(hotel),XContentType.JSON);
request.add(indexRequest);
}
restHighLevelClient.bulk(request,RequestOptions.DEFAULT);
return hotelList.size();
}
结果展示:
三:酒店搜索和分页
由此可以知道,我们这个请求的信息如下:
- 请求方式:POST
- 请求路径:/hotel/list
- 请求参数:JSON对象,包含4个字段:
- key:搜索关键字
- page:页码
- size:每页大小
- sortBy:排序
- 返回值:分页查询,需要返回分页结果PageResult,包含两个属性:
- `total`:总条数
- `List<Hotel>`:当前页的数据
后端代码展示:
web层:
@PostMapping("/list")
public PageResult list(@RequestBody RequestParams requestParams) throws IOException {
return hotelService.list(requestParams);
}
这里为了以后方便使用,对解析数据和Builder进行了抽取
serviceImpl层:
@Override
public PageResult list(RequestParams requestParams) throws IOException {
SearchRequest request = new SearchRequest("hotel");
SearchSourceBuilder builder = buildBasicQuery(requestParams);
request.source(builder);
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
PageResult pageResult = handlerResponse(response);
return pageResult;
}
//使用bool是因为用户的搜索是多条件的,可进行多条件搜索。
private SearchSourceBuilder buildBasicQuery(RequestParams requestParams){
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
if (StringUtils.isEmpty(requestParams.getKey())) {
boolBuilder.must(QueryBuilders.matchAllQuery());
}else {
boolBuilder.must(QueryBuilders.matchQuery("all",requestParams.getKey()));
boolBuilder.filter(QueryBuilders.termQuery("all",requestParams.getKey()));
}
builder.query(boolBuilder);
builder.from((requestParams.getPage()-1)*requestParams.getSize());
builder.size(requestParams.getSize());
}
/**
* 抽取解析相应的数据
* 解析数据(总数,数据列表,高亮的字段)
*/
private PageResult handlerResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
//获取总数
long total = searchHits.getTotalHits().value;
//获取所有文档数据
SearchHit[] hits = searchHits.getHits();
List<Hotel> hotels = new ArrayList<>();
//循环遍历
for (SearchHit hit : hits) {
//获取source
String json = hit.getSourceAsString();
//解析成Hotel对象,并没有高亮的处理
Hotel hotel = JSON.parseObject(json, Hotel.class);
//添加到hotels中
hotels.add(hotel);
}
return new PageResult(total,hotels);
}
四:酒店结果过滤
后端代码展示:
在serviceImpl层,新增代码;
//完整代码
private SearchSourceBuilder buildBasicQuery(RequestParams requestParams){
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
if (StringUtils.isEmpty(requestParams.getKey())) {
boolBuilder.must(QueryBuilders.matchAllQuery());
}else {
boolBuilder.must(QueryBuilders.matchQuery("all",requestParams.getKey()));
boolBuilder.filter(QueryBuilders.termQuery("all",requestParams.getKey()));
}
if (BeanUtil.isNotEmpty(requestParams.getCity())){
boolBuilder.must(QueryBuilders.matchQuery("city",requestParams.getCity()));
}
if (BeanUtil.isNotEmpty(requestParams.getBrand())){
boolBuilder.must(QueryBuilders.matchQuery("brand",requestParams.getBrand()));
}
if (BeanUtil.isNotEmpty(requestParams.getStarName())){
boolBuilder.must(QueryBuilders.matchQuery("starName",requestParams.getStarName()));
}
if (requestParams.getMaxPrice() != null && requestParams.getMinPrice() != null){
boolBuilder.must(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
}
builder.query(boolBuilder);
builder.from((requestParams.getPage()-1)*requestParams.getSize());
builder.size(requestParams.getSize());
return builder;
}
五:高亮展示
后端代码展示:
在serviceImpl层,新增代码:
HighlightBuilder highlighter = new HighlightBuilder();
highlighter.preTags("<font color=red>");
highlighter.postTags("</font>");
highlighter.field("name");
highlighter.requireFieldMatch(false);
builder.highlighter(highlighter);
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
Hotel hotel = JSON.parseObject(source, Hotel.class);
Map<String, HighlightField> map = hit.getHighlightFields();
HighlightField highlightField = map.get("name");
if (highlightField != null){
String name = highlightField.getFragments()[0].toString();
hotel.setName(name);
}
hotels.add(hotel);
}
六:酒店竞价排名
广告优先
后端代码展示:
FunctionScoreQueryBuilder scoreQuery = QueryBuilders.functionScoreQuery(boolBuilder, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("ad", 1),
ScoreFunctionBuilders.weightFactorFunction(10))});
builder.query(scoreQuery);
serviceImpl完整代码展示:
package cn.itcast.hotel.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.itcast.hotel.domain.Hotel;
import cn.itcast.hotel.domain.PageResult;
import cn.itcast.hotel.domain.RequestParams;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.service.HotelService;
import com.alibaba.fastjson.JSON;
import org.apache.lucene.search.QueryCache;
import org.apache.lucene.util.QueryBuilder;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
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.common.xcontent.XContentType;
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.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class HotelServiceImpl implements HotelService {
@Autowired
private HotelMapper hotelMapper;
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
public Integer init() throws IOException {
List<Hotel> hotelList = hotelMapper.selectList(null);
BulkRequest request = new BulkRequest();
for (Hotel hotel : hotelList) {
IndexRequest indexRequest = new IndexRequest("hotel");
indexRequest.id(hotel.getId().toString());
indexRequest.source(JSON.toJSONString(hotel),XContentType.JSON);
request.add(indexRequest);
}
restHighLevelClient.bulk(request,RequestOptions.DEFAULT);
return hotelList.size();
}
@Override
public PageResult list(RequestParams requestParams) throws IOException {
SearchRequest request = new SearchRequest("hotel");
SearchSourceBuilder builder = buildBasicQuery(requestParams);
request.source(builder);
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
PageResult pageResult = handlerResponse(response);
return pageResult;
}
private SearchSourceBuilder buildBasicQuery(RequestParams requestParams){
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
if (StringUtils.isEmpty(requestParams.getKey())) {
boolBuilder.must(QueryBuilders.matchAllQuery());
}else {
boolBuilder.must(QueryBuilders.matchQuery("all",requestParams.getKey()));
boolBuilder.filter(QueryBuilders.termQuery("all",requestParams.getKey()));
}
if (BeanUtil.isNotEmpty(requestParams.getCity())){
boolBuilder.must(QueryBuilders.matchQuery("city",requestParams.getCity()));
}
if (BeanUtil.isNotEmpty(requestParams.getBrand())){
boolBuilder.must(QueryBuilders.matchQuery("brand",requestParams.getBrand()));
}
if (BeanUtil.isNotEmpty(requestParams.getStarName())){
boolBuilder.must(QueryBuilders.matchQuery("starName",requestParams.getStarName()));
}
if (requestParams.getMaxPrice() != null && requestParams.getMinPrice() != null){
boolBuilder.must(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
}
//builder.query(boolBuilder);
FunctionScoreQueryBuilder scoreQuery = QueryBuilders.functionScoreQuery(boolBuilder, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("ad", 1),
ScoreFunctionBuilders.weightFactorFunction(10))});
builder.query(scoreQuery);
builder.from((requestParams.getPage()-1)*requestParams.getSize());
builder.size(requestParams.getSize());
HighlightBuilder highlighter = new HighlightBuilder();
highlighter.preTags("<font color=red>");
highlighter.postTags("</font>");
highlighter.field("name");
highlighter.requireFieldMatch(false);
builder.highlighter(highlighter);
return builder;
}
/**
* 抽取解析相应的数据
* 解析数据(总数,数据列表,高亮的字段)
*/
private PageResult handlerResponse(SearchResponse response) {
SearchHit[] hits = response.getHits().getHits();
ArrayList<Hotel> hotels = new ArrayList<>();
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
Hotel hotel = JSON.parseObject(source, Hotel.class);
Map<String, HighlightField> map = hit.getHighlightFields();
HighlightField highlightField = map.get("name");
if (highlightField != null){
String name = highlightField.getFragments()[0].toString();
hotel.setName(name);
}
hotels.add(hotel);
}
return new PageResult(response.getHits().getTotalHits().value, hotels);
}
}
好了,第一天学习完毕