1、准备工作
Elasticsearch对于JDK、中文词库、spring-data-elasticsearch的版本有要求
中文词库的版本需要和Elasticsearch的版本一模一样,spring-data-elasticsearch的版本需要为4.3.4.
Elasticsearch 7.15.2需要JDK11的环境,如果你的电脑JDK版本是JDK8,则需要使用Elasticsearch自带的JDK。打开bin目录,修改elasticsearch-env.bat文件中选择Java环境的部分如下:
rem comparing to empty string makes this equivalent to bash -v check on env var
rem and allows to effectively force use of the bundled jdk when launching ES
rem by setting JAVA_HOME=
set JAVA="%ES_HOME%\jdk\bin\java.exe"
set JAVA_HOME="%ES_HOME%\jdk"
set JAVA_TYPE=bundled jdk
如果你使用的是windows系统,那么config目录下的elasticsearch.yml中默认的Linux路径设置需要更改为windows格式的:
# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: D:\EnvironmentNewCoder\data\elasticsearch-7.15.2\data
#
# Path to log files:
#
path.logs: D:\EnvironmentNewCoder\data\elasticsearch-7.15.2\logs
2、配置elasticsearch高级客户端RestHighLevelClient
@Configuration
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
ClientConfiguration configuration = ClientConfiguration.builder()
.connectedTo("127.0.0.1:9200")
.build();
RestHighLevelClient client = RestClients.create(configuration).rest();
return client;
}
}
3、在想查询的实体类上增加响应的注解
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "discusspost")//indexName:索引名,type:固定_doc,shards:分片,replicas:备份
public class DiscussPost {
@Id
private int id;
@Field(type = FieldType.Integer)
private int userId;
//analyzer:互联网校招--->建立最大的索引(就是各种拆分)
//searchAnalyzer 拆分尽可能少的满足意图的分词器
@Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_smart")
private String title;
@Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_smart")
private String content;
@Field(type = FieldType.Integer)
//0-普通; 1-置顶;
private int type;//0-普通; 1-置顶;
@Field(type = FieldType.Integer)
private int status;//0-正常; 1-精华; 2-拉黑;
@Field(type = FieldType.Date)
private Date createTime;
@Field(type = FieldType.Integer)
private int commentCount;
@Field(type = FieldType.Double)
private double score;//帖子分数
}
4、创建搜索业务层
由于需要分页显示,我们需要查询该关键词的总行数,并且高亮显示关键词。增加实体类对象和删除实体类对象时需要在Elasticsearch中通过消息队列异步的增加和删除。
@Service
public class ElasticsearchService {
@Autowired
private DiscussPostRepository discussPostRepository;
@Autowired
private RestHighLevelClient restHighLevelClient;
//保存
public void saveDiscussPost(DiscussPost post){
discussPostRepository.save(post);
}
//删除
public void deleteDiscussPost(int id){
discussPostRepository.deleteById(id);
}
//查询
public SearchHit[] searchDiscussPost(String keyword, int current, int limit) throws IOException {
SearchRequest searchRequest=new SearchRequest("discusspost");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();//构造搜索条件
//2.查询title\content中含有keyWord的
sourceBuilder.query(QueryBuilders.multiMatchQuery(keyword,"title"));
sourceBuilder.query(QueryBuilders.multiMatchQuery(keyword,"content"));
//3.分页查询
sourceBuilder.from(current).size(limit);
//4.按照规则排列
sourceBuilder.sort(SortBuilders.fieldSort("type").order(SortOrder.DESC));
sourceBuilder.sort(SortBuilders.fieldSort("score").order(SortOrder.DESC));
sourceBuilder.sort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
//5.给指定字段加上指定高亮样式
HighlightBuilder highlightBuilder=new HighlightBuilder();
highlightBuilder.field("title").preTags("<span style='color:red;'>").postTags("</span>");
highlightBuilder.field("content").preTags("<span style='color:red;'>").postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse=restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
return searchResponse.getHits().getHits();
}
//查询总数
public int searchDiscussPostCount(String keyWord) throws IOException {
SearchRequest searchRequest=new SearchRequest("discusspost");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();//构造搜索条件
sourceBuilder.query(QueryBuilders.multiMatchQuery(keyWord,"content","title"));
sourceBuilder.size(10000);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse=restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
return searchResponse.getHits().getHits().length;
}
}
5、搜索业务控制层
调用搜索业务后,Elasticsearch给我们返回的都是JSON字符串我们需要将其再转换回实体类对象,如果前端显示出现问题,我们可以考虑加上防止html转义的操作。
@Controller
public class SearchController implements CommunityConstant {
@Resource
private ElasticsearchService elasticsearchService;
@Resource
private UserService userService;
@Resource
private LikeService likeService;
@RequestMapping(path = "/search", method = RequestMethod.GET)
public String search(String keyWord, Page page, Model model) throws IOException {
// 搜索帖子
SearchHit[] searchHits =
elasticsearchService.searchDiscussPost(keyWord, page.getCurrent() - 1, page.getLimit());
// 聚合数据
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (searchHits != null) {
for (SearchHit hit: searchHits) {
Map<String, Object> map = new HashMap<>();
// 帖子
//防止html转义
//String postJSON = HtmlUtils.htmlUnescape(hit.getSourceAsString());
DiscussPost post = JSONObject.parseObject(hit.getSourceAsString(), DiscussPost.class);
map.put("post", post);
// 作者
map.put("user", userService.findUserById(post.getUserId()));
// 点赞数量
map.put("likeCount", likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId()));
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
model.addAttribute("keyWord", keyWord);
// 分页信息
page.setPath("/search?keyWord=" + keyWord);
page.setRows(searchHits == null ? 0 : elasticsearchService.searchDiscussPostCount(keyWord));
return "/site/search";
}
}