1. 项目背景
处于失业找工作的阶段,随便写写吧~ 没啥背景,没啥意义,Java后端越来越卷了。第一学历不是本科,感觉真的是没有一点路可走。
如果有路过的小伙伴,如果身边还有坑位,不限第一学历的话,麻烦帮忙捞一下!非常感谢!
2. 项目环境
说下项目环境吧,尽量使用和自己SpringBoot匹配的版本,不然整合的过程中可能会有很多的坑~
本文环境如下:
Springboot:2.7.12
JDK:1.8
elasticsearch:7.17.3
3. 下载安装
贴个ES下载链接,windows环境:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.3-windows-x86_64.zip
然后还一个,相当于ES的可视化界面吧:https://github.com/mobz/elasticsearch-head
windows环境下很方便,这里就不多说了,简单说说需要修改的地方吧
3.1 elasticsearch配置
安装就是下载解压,官网下载速度很快,下载完直接解压就行。解压完大致如下:
然后配置一下环境变量,ES_HOME
和ES_JAVA_HOME
,注意改成自己的解压目录哈 ~
ES_HOME: D:\software\elasticsearch-7.17.3
ES_JAVA_HOME: D:\software\elasticsearch-7.17.3\jdk
然后修改下/config
下的elasticsearch.yml
文件,最后添加一下内容,不然使用elasticsearch-head
会报跨域的错误
http.cors.enabled: true
http.cors.allow-origin: "*"
好了然后直接前往/bin
目录下直接双击elasticsearch.bat
文件即可启动,默认9200端口。
启动后直接去浏览器输入http://127.0.0.1:9200/
,访问到下面页面代表ES启动成功。
3.2 elasticsearch-head配置
elasticsearch-head
直接在github上拉代码就行。
这个依赖node环境,需要使用npm下载依赖。
进入到D:\software\elasticsearch-head
,先通过npm i
安装依赖,然后通过npm run start
启动。
npm i
npm run start
启动访问http://127.0.0.1:9100/
,点击了连接,可以看到以下界面 ,我的有点不纯了😂
4. SpringBoot 集成Es
4.1 添加依赖
在Springboot中pom.xml中添加ES依赖
<!-- 引入ES依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
4.2 项目使用
我这里直接用了笑小枫网站博客的内容,方便后面演示。
首先创建一个对象,用来定义存储数据的对象,对应ES中的mappings
。对象如下:
package com.maple.es.bean;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
import java.util.List;
/**
* @author 笑小枫 <https://www.xiaoxiaofeng.com/>
* @date 2023/1/30
*/
@Data
@Document(indexName = "blog_title")
public class BlogDoc {
@Id
private Long id;
@Field(type = FieldType.Long)
private Long categoryId;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Text)
private String content;
@Field(type = FieldType.Keyword)
private String description;
@Field(type = FieldType.Keyword)
private String keywords;
@Field(type = FieldType.Keyword)
private List<String> keywordList;
private String status;
@Field(type = FieldType.Integer)
private Integer readNum;
@Field(type = FieldType.Integer)
private Integer collectNum;
@Field(type = FieldType.Date)
private Date createTime;
}
ES中对象类型这里只演示一下String、数值和时间。
创建BlogRepository
继承ElasticsearchRepository<BlogDoc, Long>
接口。
其中BlogDoc
是我们上面的对象。Long对应我们的主键,标 @Id
的那个。
你可以在里面定义一些接口,也可以不定义。我这里只用到了继承的简单增删接口,查询使用ElasticsearchRestTemplate
。
package com.maple.es.repository;
import com.maple.es.bean.BlogDoc;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface BlogRepository extends ElasticsearchRepository<BlogDoc, Long> {
}
直接上BlogServiceImpl
吧 ,详细的注释都放在代码里了,通义千问生成,大体懂。
核心部分看查询getList
方法,涉及到分页了,简单定义了一个分页的对象
Page对象
package com.maple.es.bean;
import lombok.Data;
@Data
public class Page {
private Integer page;
private Integer size;
private Long total;
}
简单的封装一下分页对象、请求参数和返回结果的对象
package com.maple.es.bean;
import lombok.Data;
import java.util.List;
@Data
public class BlogPageDoc {
private Page page;
private BlogDoc query;
private List<BlogDoc> result;
}
【本文重点】Service实现类
package com.maple.es.service.impl;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.maple.es.bean.BlogDoc;
import com.maple.es.bean.BlogPageDoc;
import com.maple.es.bean.Page;
import com.maple.es.mapper.BlogMapper;
import com.maple.es.repository.BlogRepository;
import com.maple.es.service.BlogService;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Service
public class BlogServiceImpl implements BlogService {
@Autowired
private BlogRepository blogRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Autowired
private BlogMapper blogTitleMapper;
/**
* 创建单条数据
* <p>
* 该方法负责将给定的BlogDoc对象保存到ES中
*
* @param blogBean 代表要创建的博客文章的文档对象
*/
@Override
public void createBlog(BlogDoc blogBean) {
blogRepository.save(blogBean);
}
/**
* 更新博客文档
* <p>
* 使用Elasticsearch的脚本更新功能来更新博客文档的标题和内容
*
* @param blogBean 包含要更新的博客信息的BlogDoc对象,包括博客的标题、内容和ID
*/
@Override
public void updateBlog(BlogDoc blogBean) {
// 构建更新脚本,用于更新Elasticsearch文档的标题和内容
// ctx._source 固定写法
String script = "ctx._source.title = '" + blogBean.getTitle() + "';" +
"ctx._source.content = '" + blogBean.getTitle() + "'";
// 构建更新查询对象,指定要更新的文档ID和更新脚本
UpdateQuery build = UpdateQuery.builder(String.valueOf(blogBean.getId())).withScript(script).build();
// 指定要更新的索引名称
IndexCoordinates indexCoordinates = IndexCoordinates.of("blog_title");
// 执行更新操作,并获取更新响应
elasticsearchRestTemplate.update(build, indexCoordinates);
}
/**
* 初始化数据方法
* <p>
* 该方法用于从数据库中加载博客标题的相关信息,并处理关键词字符串,以便于后续的搜索或展示
* 它首先从数据库中选择所有博客标题的信息,然后遍历这些信息,将每个博客标题的关键词字符串
* 转换为关键词列表,最后将处理后的博客标题信息保存到博客仓库中
*/
public void initData() {
// 从数据库中选择所有博客标题的信息
List<BlogDoc> list = blogTitleMapper.selectTitleList();
// 遍历博客标题列表,处理每个博客标题的关键词
for (BlogDoc blogTitleVo : list) {
// 如果博客标题的关键词不为空,则将其转换为关键词列表
if (blogTitleVo.getKeywords() != null) {
blogTitleVo.setKeywordList(Arrays.asList(blogTitleVo.getKeywords().split(",")));
}
}
// 将处理后的博客标题列表保存到博客仓库中
blogRepository.saveAll(list);
}
/**
* 删除指定ID的博客
*
* @param id 博客的唯一标识符
*/
@Override
public void deleteBlog(Long id) {
blogRepository.deleteById(id);
}
@Override
public BlogPageDoc getListBlogTitle(BlogPageDoc blogTitleVo) {
return getList(blogTitleVo);
}
/**
* 根据查询条件和分页信息获取博客列表
*
* @param blogPageDoc 包含查询条件和分页信息的博客页面文档
* @return 返回填充了查询结果的博客页面文档
*/
private BlogPageDoc getList(BlogPageDoc blogPageDoc) {
// 获取查询条件
BlogDoc blogDoc = blogPageDoc.getQuery();
// 获取分页信息
Page page = blogPageDoc.getPage();
// 创建高亮构建器
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 设置需要高亮的字段
highlightBuilder.field("title");
highlightBuilder.field("description");
// 设置高亮显示的前缀和后缀
highlightBuilder.preTags("<span style=\"font-color :red\">");
highlightBuilder.postTags("</span>");
// 创建布尔查询构建器
BoolQueryBuilder queryBuilders = QueryBuilders.boolQuery();
// 如果标题关键字不为空,则必须匹配keywordList中的关键字
if (StringUtils.isNotBlank(blogDoc.getTitle())) {
// list中有一个匹配成功,就代表成功,虽然指定了keywordList的类型,但是在es中还是text(不知道是不是哪个地方bug了,暂未验证),所以指定keywordList.keyword
queryBuilders.must(QueryBuilders.termsQuery("keywordList.keyword", blogDoc.getKeywordList()));
}
// 如果类别ID不为空,则必须匹配类别ID
if (blogDoc.getCategoryId() != null) {
queryBuilders.must(QueryBuilders.termQuery("categoryId", blogDoc.getCategoryId()));
}
// 如果blogDoc的阅读数量不为空,则在查询中添加阅读数量的范围条件
if (blogDoc.getReadNum() != null) {
// 构建一个范围查询,筛选出阅读数量大于当前博客阅读数量的文档
queryBuilders.must(QueryBuilders.rangeQuery("readNum").gt(blogDoc.getReadNum()));
}
// 如果标题不为空,则应匹配标题、描述或内容中的标题
if (StringUtils.isNotBlank(blogDoc.getTitle())) {
queryBuilders.should(QueryBuilders.matchQuery("title", blogDoc.getTitle()));
queryBuilders.should(QueryBuilders.matchQuery("description", blogDoc.getTitle()));
queryBuilders.should(QueryBuilders.matchQuery("content", blogDoc.getTitle()));
// 设置至少应匹配的条件数
queryBuilders.minimumShouldMatch(1);
}
// 执行搜索并获取结果
SearchHits<BlogDoc> searchHits = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
.withQuery(queryBuilders) // 设置查询条件
.withSorts(SortBuilders.fieldSort("readNum").order(SortOrder.DESC)) // 设置排序条件
.withHighlightBuilder(highlightBuilder) // 设置高亮构建器
.withPageable(PageRequest.of(page.getPage() - 1, page.getSize())) // 设置分页信息
.build(),
BlogDoc.class);
// 创建结果列表
List<BlogDoc> result = new ArrayList<>();
// 设置总记录数
page.setTotal(searchHits.getTotalHits());
// 遍历搜索结果并处理高亮显示
searchHits.getSearchHits().forEach(hits -> {
BlogDoc blogTitle = hits.getContent();
Map<String, List<String>> highlightFields = hits.getHighlightFields();
// 如果标题的高亮字段不为空,则替换标题
if (!CollectionUtils.isEmpty(highlightFields.get("title"))) {
blogTitle.setTitle(hits.getHighlightFields().get("title").get(0));
}
// 如果描述的高亮字段不为空,则替换描述
if (!CollectionUtils.isEmpty(highlightFields.get("description"))) {
blogTitle.setDescription(hits.getHighlightFields().get("description").get(0));
}
// 将处理后的结果添加到列表中
result.add(blogTitle);
});
// 设置查询结果
blogPageDoc.setResult(result);
// 返回填充了查询结果的博客页面文档
return blogPageDoc;
}
}
可以在elasticsearch-head
看到对应的数据,这里就不一步一步演示了
5.结语
文中只放了一些核心的代码
一些调用数据的代码和Controller的代码都没贴,非核心
需要的小伙伴可以前往github拉源码看一下。
本文源码:https://github.com/hack-feng/maple-product/tree/main/maple-es
🐾我是笑小枫,全网皆可搜的【笑小枫】