文末附上项目的下载地址
1、项目代码结构
这里暂不将涉及消息中间件的类放进去。因为是采用的是ES8的RestAPI,所以和黑马程序员的demo有些差距,相关注释没有那么详细。
2、mapperd代码
package com.toto.es.hotel.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.toto.es.hotel.pojo.Hotel;
public interface HotelMapper extends BaseMapper<Hotel> {
}
3、Hotel代码
package com.toto.es.hotel.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(type = IdType.INPUT)
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 longitude;
private String latitude;
private String pic;
}
4、HotelDoc代码
package com.toto.es.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.*;
@Data
@NoArgsConstructor
public class HotelDoc {
private String 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 Double distance;
private Boolean isAD; // 是否是广告
private List<String> suggestion;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId().toString();
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();
if(this.business.contains("/")){
String[] arr = this.business.split("/");
this.suggestion = new ArrayList<>();
this.suggestion.add(this.brand);
Collections.addAll(this.suggestion, arr);
}else if(this.business.contains("、")){
String[] arr = this.business.split("、");
this.suggestion = new ArrayList<>();
this.suggestion.add(this.brand);
Collections.addAll(this.suggestion, arr);
}else{
this.suggestion = Arrays.asList(this.brand, this.business);
}
}
}
5、PageResult代码
package com.toto.es.hotel.pojo;
import lombok.Data;
import java.util.List;
@Data
public class PageResult {
private Long total;
private List<HotelDoc> hotels;
public PageResult() {
}
public PageResult(Long total, List<HotelDoc> hotels) {
this.total = total;
this.hotels = hotels;
}
}
6、RequestParams代码
package com.toto.es.hotel.pojo;
import lombok.Data;
@Data
public class RequestParams {
private String key;
private Integer page;
private Integer size;
private String sortBy;
private String city;
private String brand;
private String starName;
private String minPrice;
private String maxPrice;
private String location;
}
7、IHotelService代码
package com.toto.es.hotel.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.toto.es.hotel.pojo.Hotel;
import com.toto.es.hotel.pojo.PageResult;
import com.toto.es.hotel.pojo.RequestParams;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public interface IHotelService extends IService<Hotel> {
PageResult search(RequestParams params);
Map<String, List<String>> filters(RequestParams params);
Map<String, List<String>> filters2();
List<String> getsuggestions(String prefix);
void insertById(Long id);
void deleteById(Long id);
}
8、HotelService代码(基于ES8调整)
package com.toto.es.hotel.service.impl;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.mapping.GeoPointProperty;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.DeleteResponse;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.NamedValue;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.toto.es.hotel.mapper.HotelMapper;
import com.toto.es.hotel.pojo.Hotel;
import com.toto.es.hotel.pojo.HotelDoc;
import com.toto.es.hotel.pojo.PageResult;
import com.toto.es.hotel.pojo.RequestParams;
import com.toto.es.hotel.service.IHotelService;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private ElasticsearchClient client;
@Override
public PageResult search(RequestParams params) {
SearchResponse<HotelDoc> response = null;
try {
// 1.创建请求
SearchRequest.Builder builder = new SearchRequest.Builder();
// 2.构造请求参数
buildBasicQuery(builder, params);
// 3.发送请求
response = client.search(b -> builder, HotelDoc.class);
return this.handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Map<String, List<String>> filters(RequestParams params) {
try {
// 1.创建请求
SearchRequest.Builder builder = new SearchRequest.Builder();
// 2.构造请求参数
buildBasicQuery(builder, params);
buildAggQuery(builder);
// 3.发送请求
SearchResponse<Map> response = client.search(b -> builder, Map.class);
// 4.1解析结果
Map<String, List<String>> result = new HashMap<>();
// 4.1.1 获取品牌聚合结果
List<String> brandList = getAggByName("brandAgg", response);
//result.put("品牌", brandList);
result.put("brand", brandList);
// 4.1.2 获取城市聚合结果
List<String> cityList = getAggByName("cityAgg", response);
//result.put("城市", cityList);
result.put("city", cityList);
// 4.1.3 获取星级聚合结果
List<String> starList = getAggByName("starAgg", response);
//result.put("星级", starList);
result.put("starName", starList);
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private List<String> getAggByName(String aggName, SearchResponse<Map> response){
Map<String, Aggregate> aggregations = response.aggregations();
Aggregate brandAgg = aggregations.get(aggName);
Buckets<StringTermsBucket> buckets = brandAgg.sterms().buckets();
List<String> brandList = new ArrayList<>();
for (StringTermsBucket b : buckets.array()) {
brandList.add(b.key().stringValue());
//System.err.println(b.key().stringValue() + " : " + b.docCount());
}
return brandList;
}
private void buildAggQuery(SearchRequest.Builder builder){
builder.index("hotel").size(0);
builder.aggregations("brandAgg", a -> a.terms(t -> t.field("brand").size(100)));
builder.aggregations("cityAgg", a -> a.terms(t -> t.field("city").size(100)));
builder.aggregations("starAgg", a -> a.terms(t -> t.field("starName").size(100)));
}
private void buildBasicQuery(SearchRequest.Builder builder, RequestParams params) {
// 2.1 指定索引和分页
String key = params.getKey();
int page = params.getPage();
int size = params.getSize();
String location = params.getLocation();
builder.index("hotel");
builder.from((page - 1) * size).size(size);
// 2.2 构造查询条件(复核查询)
BoolQuery.Builder boolQuery = QueryBuilders.bool();
if(key == null || "".equals(key)){
MatchAllQuery.Builder allQuery = QueryBuilders.matchAll();
//builder.query(query -> query.matchAll(matchall -> allQuery));
boolQuery.must(must -> must.matchAll(ma -> allQuery));
}else{
MatchQuery.Builder machQuery = QueryBuilders.match();
machQuery.field("all").query(key);
boolQuery.must(must -> must.match(m -> machQuery));
}
// 城市条件
if(params.getCity() != null && !"".equals(params.getCity())){
TermQuery.Builder termQuery = QueryBuilders.term();
termQuery.field("city").value(params.getCity());
boolQuery.filter(filter -> filter.term(t -> termQuery));
}
// 品牌条件
if(params.getBrand() != null && !"".equals(params.getBrand())){
TermQuery.Builder termQuery = QueryBuilders.term();
termQuery.field("brand").value(params.getBrand());
boolQuery.filter(filter -> filter.term(t -> termQuery));
}
// 星级条件
if(params.getStarName() != null && !"".equals(params.getStarName())){
TermQuery.Builder termQuery = QueryBuilders.term();
termQuery.field("starName").value(params.getStarName());
boolQuery.filter(filter -> filter.term(t -> termQuery));
}
// 价格条件
if(params.getMinPrice() != null && params.getMaxPrice() != null){
RangeQuery.Builder rangeQuery = QueryBuilders.range();
rangeQuery.field("price").gte(JsonData.fromJson(params.getMinPrice())).lte(JsonData.fromJson(params.getMaxPrice()));
boolQuery.filter(filter -> filter.range(r -> rangeQuery));
}
// 排序条件
if(params.getSortBy() != null && !"".equals(params.getSortBy())){
String sortBy = params.getSortBy();
if("score".equals(sortBy)){
builder.sort(sort -> sort.field(f -> f.field("score").numericType(FieldSortNumericType.Double).order(SortOrder.Desc)));
}else if("price".equals(sortBy)){
builder.sort(sort -> sort.field(f -> f.field("price").numericType(FieldSortNumericType.Double).order(SortOrder.Desc)));
}
}
// 我附近的排序
if(location != null && !"".equals(location)){
String[] latlon = location.split(",");
builder.sort(sort -> sort.geoDistance(geo -> geo.field("location").location(l -> l.latlon(ll -> ll.lat(Double.parseDouble(latlon[0].replace(" ",""))).lon(Double.parseDouble(latlon[1].replace(" ",""))))).unit(DistanceUnit.Kilometers).order(SortOrder.Asc)));
}
// 算分控制,置顶
Query functionScoreQuery = QueryBuilders.functionScore(fun->fun.query(q->q.bool(b -> boolQuery)).functions(fsFunctions -> fsFunctions.filter(f -> f.term(t -> t.field("isAD").value(true))).weight(10D)).boostMode(FunctionBoostMode.Multiply));
//builder.query(query -> query.bool(mach -> functionScoreQuery));
builder.query(functionScoreQuery);
}
private PageResult handleResponse(SearchResponse<HotelDoc> response) {
// System.err.println(response.hits().total().value());
long total = response.hits().total().value();
List<HotelDoc> hotels = new ArrayList<>();
for (Hit<HotelDoc> doc : response.hits().hits()) {
HotelDoc hotelDoc = doc.source();
List<FieldValue> sortList = doc.sort();
if(ObjectUtils.isNotEmpty(sortList)){
hotelDoc.setDistance(sortList.get(0).doubleValue());
}
hotels.add(hotelDoc);
}
return new PageResult(total, hotels);
}
@Override
public Map<String, List<String>> filters2() {
try {
// 1.创建请求
SearchRequest.Builder builder = new SearchRequest.Builder();
// 2.构造请求参数
buildAggQuery(builder);
// 3.发送请求
SearchResponse<Map> response = client.search(b -> builder, Map.class);
// 4.1解析结果
Map<String, List<String>> result = new HashMap<>();
// 4.1.1 获取品牌聚合结果
List<String> brandList = getAggByName("brandAgg", response);
result.put("品牌", brandList);
// 4.1.2 获取城市聚合结果
List<String> cityList = getAggByName("cityAgg", response);
result.put("城市", cityList);
// 4.1.3 获取星级聚合结果
List<String> starList = getAggByName("starAgg", response);
result.put("星级", starList);
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getsuggestions(String prefix) {
List<String> suggestions = new ArrayList<>();
try {
SearchResponse<Map> response = client.search(builder ->
builder.index("hotel")
.suggest(s -> s.suggesters("suggestions",
suggest -> suggest.prefix(prefix).completion(c -> c.field("suggestion").skipDuplicates(true).size(10)))),
Map.class);
// 获取联想建议
Map suggestMap = response.suggest();
if (suggestMap != null) {
List<?> suggestionList = (List<?>) suggestMap.get("suggestions");
if (suggestionList != null) {
for (Object suggestionInfo : suggestionList) {
if (suggestionInfo instanceof Suggestion) {
Suggestion suggestion = (Suggestion) suggestionInfo;
if (suggestion.isCompletion()) {
CompletionSuggest completionSuggest = suggestion.completion();
List<CompletionSuggestOption> options = completionSuggest.options();
for (CompletionSuggestOption option : options) {
suggestions.add(option.text());
}
}
}
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return suggestions;
}
@Override
public void insertById(Long id) {
try {
Hotel hotel = getById(id);
HotelDoc hotelDoc = new HotelDoc(hotel); // 使用实体,也可使用HashMap,还可以使用json
IndexResponse response = client.index(builder -> builder.index("hotel").id(hotelDoc.getId()).document(hotelDoc));
System.err.println(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteById(Long id) {
try {
DeleteResponse response = client.delete(builder -> builder.index("hotel").id(id.toString()));
System.err.println(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
9、HotelController代码
package com.toto.es.hotel.web;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.toto.es.hotel.pojo.Hotel;
import com.toto.es.hotel.pojo.PageResult;
import com.toto.es.hotel.pojo.RequestParams;
import com.toto.es.hotel.service.IHotelService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("hotel")
public class HotelController {
@Resource
private IHotelService hotelService;
@RequestMapping("/admin")
public String login(){
return "admin";
}
@GetMapping("/{id}")
public Hotel queryById(@PathVariable("id") Long id){
return hotelService.getById(id);
}
@PostMapping("/list")
public PageResult hotelList(@RequestBody RequestParams params) throws IOException {
return hotelService.search(params);
}
@PostMapping("/filters")
public Map<String, List<String>> hotelfilters(@RequestBody RequestParams params) throws IOException {
return hotelService.filters(params);
}
@GetMapping("/suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix){
return hotelService.getsuggestions(prefix);
}
@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
hotelService.save(hotel);
}
@PutMapping()
public void updateById(@RequestBody Hotel hotel){
if (hotel.getId() == null) {
throw new InvalidParameterException("id不能为空");
}
hotelService.updateById(hotel);
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") Long id) {
hotelService.removeById(id);
}
}
10、HotelDemoApplication代码(启动类)
package com.toto.es.hotel;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@MapperScan(basePackages = "com.toto.es.hotel.mapper")
@SpringBootApplication
public class HotelDemoApplication {
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(HotelDemoApplication.class,args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
log.info("\n----------------------------------------------------------\n\t" +
//"ElasticSearch Demo: \thttp://localhost" + ip + ":" + port + "\n" +
"ElasticSearch Demo: \thttp://localhost:" + port + "\n" +
"----------------------------------------------------------");
openE("http://"+ ip +":" + port + "");
}
private static void openE(String url) {
String runCmd = "cmd /c start " + url;
Runtime run = Runtime.getRuntime();
try {
run.exec(runCmd);
} catch (Exception e) {
System.out.println("启动项目自动打开浏览器失败");
}
}
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ElasticsearchClient slasticsearchClient() {
BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
credsProv.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "elastic"));
RestClient restClient = RestClient.builder(HttpHost.create("http://127.0.0.1:9200"))
.setHttpClientConfigCallback(hc -> hc.setDefaultCredentialsProvider(credsProv))
.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
11、application.yaml代码
server:
port: 8089
spring:
datasource:
url: jdbc:mysql://127.0.0.1:23306/totograin?useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
logging:
level:
com.toto: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
type-aliases-package: com.toto.es.hotel.pojo
12、logback.xml代码
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="consoleLog" />
</root>
</configuration>