分析
分词倒排索引分析
高亮分析
搜索的接口
- 此文章只做攻略接口的演示,其他雷同~
初始化数据分析
效果图
- 输入广州,将从elasticsearch对应的索引中查询含有此关键字的文章
- 目的地和游记都是用同样的操作,此文章只做搜索攻略文章的演示操作
- 将输入关键字高亮显示出来~
准备工作
- 我的习惯是单独为es做一个包,里面存放对应的包和类
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
application.properties
#elasticsearch
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
spring.data.elasticsearch.repositories.enable=true
qo类
- 公用父类qo
- 页面搜索操作的qo
- 划红线才是本次演示类型,首页搜索有几种,不同搜索范围则对应不同类型
实体类
mysql对应的实体类
- 将画红线的作为被es搜索的属性,所以从这些属性搞出对应的es实体对象,去对应es中的数据库
es对应的实体类
- 这es对应类的属性是由mysql攻略文章对象分析而来的,将搜索攻略文章中所需要做条件搜索的属性分析出来作为es对应的实体类,贴上注解后,启动启动类,即可在es中生成对应的索引和类,但是无对象数据,所以需要做初始化,把mysql的数据初始化到es中即可~
- 搜索的关键字 是搜攻略文章中的
title
,subTitle
,summary
中含有而得出 - @Document 指定es中的索引和类
@Field(index=true,analyzer="ik_max_word",searchAnalyzer="ik_max_word",type = FieldType.Text)
表示 细分词搜索,
@Getter
@Setter
@Document(indexName="trip_strategy",type="strategy")
public class StrategyEs implements Serializable {
public static final String INDEX_NAME = "trip_strategy";
public static final String TYPE_NAME = "strategy";
@Id
@Field( index = false,type = FieldType.Long)
private Long id;
@Field(index=true,analyzer="ik_max_word",searchAnalyzer="ik_max_word",type = FieldType.Text)
private String title;
@Field(index=true,analyzer="ik_max_word",searchAnalyzer="ik_max_word",type = FieldType.Text)
private String subTitle;
@Field(index=true,analyzer="ik_max_word",searchAnalyzer="ik_max_word",type = FieldType.Text)
private String summary;
}
repository
- 规范实现ElasticsearchRepository接口,即可对es进行jpa规范的操作
public interface StrategyEsRepository extends ElasticsearchRepository<StrategyEs, String> {
}
serviceImpl
@Service
public class StrategyEsServiceImpl implements IStrategyEsService {
@Autowired
private StrategyEsRepository repository;
@Override
public void save(StrategyEs strategyEsEs) {
repository.save(strategyEsEs);
}
@Override
public void update(StrategyEs strategyEsEs) {
repository.save(strategyEsEs);
}
@Override
public StrategyEs get(String id) {
return repository.findById(id).orElse(null);
}
@Override
public List<StrategyEs> list() {
List<StrategyEs> list = new ArrayList<>();
Iterable<StrategyEs> all = repository.findAll();
all.forEach(a -> list.add(a));
return list;
}
@Override
public void delete(String id) {
repository.deleteById(id);
}
}
搜索文章的控制器接口
- 先对搜索的关键字进行解码
- 判断qo的值,从而判断要搜索的类型范围,则使用相应的方法去查(这里做攻略文章搜索类型进行演示)
@GetMapping("/q")
public Object search(SearchQueryObject qo) throws UnsupportedEncodingException {
String kw = URLDecoder.decode(qo.getKeyword(), "UTF-8");
qo.setKeyword(kw);
switch (qo.getType()) {
case SearchQueryObject.TYPE_DEST:
return this.searchDest(qo);
case SearchQueryObject.TYPE_STRATEGY:
return this.searchStrategy(qo);
case SearchQueryObject.TYPE_TRAVEL:
return this.searchTravel(qo);
case SearchQueryObject.TYPE_USER:
return this.searchUser(qo);
default:
return this.searchAll(qo);
}
}
- 对应的搜索,搜攻略文章的方法
- 调用方法,输入参数一:
要搜索es的索引
,参数二:es索引中的类型
,参数三:对应要搜索出来的的mysql 的实体类
,参数四:es实体类中被搜索的内容字段名
private Object searchStrategy(SearchQueryObject qo) {
Page<Strategy> page = searchService.searchWithHighlight(StrategyEs.INDEX_NAME, StrategyEs.TYPE_NAME, Strategy.class, qo, "title", "subTitle", "summary");
Map<String, Object> map = new HashMap<>();
map.put("page", page);
map.put("qo", qo);
return JsonResult.success(map);
}
业务方法实现类(重点学习拷贝)
public interface ISearchService {
<T> Page<T> searchWithHighlight(String index, String type, Class<T> clz, SearchQueryObject qo, String... fields);
}
- 实现类(要学会拷贝)
- 这里引入了游记,攻略等其他业务层对象(但此文章做的是攻略演示)
- 传入es的索引,类型,和qo,以及要被搜索的内容字段,返回一个与es对象对应的mysql数据对象集合
@Service
public class SearchServiceImpl implements ISearchService {
@Autowired
private IUserInfoService userInfoService;
@Autowired
private IStrategyService strategyService;
@Autowired
private ITravelService travelService;
@Autowired
private IDestinationService destinationService;
@Autowired
private ElasticsearchTemplate template;
@Override
public <T> Page<T> searchWithHighlight(String index, String type,
Class<T> clz, SearchQueryObject qo,
String... fields) {
NativeSearchQueryBuilder searchQuery = new NativeSearchQueryBuilder();
searchQuery.withIndices(index)
.withTypes(type);
searchQuery.withQuery(QueryBuilders.multiMatchQuery(qo.getKeyword(), fields));
Pageable pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize(),
Sort.Direction.ASC, "_id");
searchQuery.withPageable(pageable);
String preTags = "<span style='color:red;'>";
String postTags = "</span>";
HighlightBuilder.Field[] fs = new HighlightBuilder.Field[fields.length];
for(int i = 0; i < fs.length; i++){
fs[i] = new HighlightBuilder.Field(fields[i])
.preTags(preTags)
.postTags(postTags);
}
searchQuery.withHighlightFields(fs);
return template.queryForPage(searchQuery.build(),clz, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<T> list = new ArrayList<>();
SearchHits hits = response.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit searchHit : searchHits) {
T t = mapSearchHit(searchHit, clazz);
Map<String, String> map = highlightFieldsCopy(searchHit.getHighlightFields(), fields);
try {
BeanUtils.copyProperties(t, map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
list.add(t);
}
AggregatedPage<T> result = new AggregatedPageImpl<>(list, pageable, response.getHits().getTotalHits());
return result;
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> clz) {
String id = searchHit.getSourceAsMap().get("id").toString();
T t = null;
if(clz == UserInfo.class){
t = (T) userInfoService.getById(id);
}else if(clz == Travel.class){
t = (T) travelService.getById(id);
}else if(clz == Strategy.class){
t = (T) strategyService.getById(id);
}else if(clz == Destination.class){
t = (T) destinationService.getById(id);
}else{
t= null;
}
return t;
}
});
}
private Map<String, String> highlightFieldsCopy(Map<String, HighlightField> map, String ...fields){
Map<String, String> mm = new HashMap<>();
for (String field : fields) {
HighlightField hf = map.get(field);
if (hf != null) {
Text[] fragments = hf.fragments();
String str = "";
for (Text text : fragments) {
str += text;
}
mm.put(field, str);
}
}
return mm;
}
}
mysql对elasticsearch初始化数据
- 因为es实体类是通过mysql实体类分析而来的,所以做初始化数据,从mysql对象转化成es对象 并保存进es数据库中即可~
- 为了方便演示,这里使用接口做初始化,也可以用监听器弄
- 注入mysql和es的业务层对象,即可操作mysql和es数据库
- 将mysql 的对象数据集合遍历,将里面每一个对象都转成es对象,然后存入es中
@RestController
public class DataController {
@Autowired
private IDestinationEsService destinationEsService;
@Autowired
private IStrategyEsService strategyEsService;
@Autowired
private ITravelEsService travelEsService;
@Autowired
private IUserInfoEsService userInfoEsService;
@Autowired
private IDestinationService destinationService;
@Autowired
private IStrategyService strategyService;
@Autowired
private ITravelService travelService;
@Autowired
private IUserInfoService userInfoService;
@ApiIgnore
@GetMapping("/dataInit")
public Object dataInit(){
List<Strategy> sts = strategyService.list();
for (Strategy st : sts) {
StrategyEs es = new StrategyEs();
BeanUtils.copyProperties(st, es);
strategyEsService.save(es);
}
List<Travel> ts = travelService.list();
for (Travel t : ts) {
TravelEs es = new TravelEs();
BeanUtils.copyProperties(t, es);
travelEsService.save(es);
}
List<UserInfo> uf = userInfoService.list();
for (UserInfo u : uf) {
UserInfoEs es = new UserInfoEs();
BeanUtils.copyProperties(u, es);
userInfoEsService.save(es);
}
List<Destination> dests = destinationService.list();
for (Destination d : dests) {
DestinationEs es = new DestinationEs();
BeanUtils.copyProperties(d, es);
destinationEsService.save(es);
}
return "ok";
}
}