运用策略模式来实现Mysql和ELasticSearch的搜索,根据数据库中查询出来的结果然后通过SearchEnum枚举类获取到项目中需要的策略模式(也就是mysqlStrategyImpl或者elasticsearchStrategyImpl)。
/**
* @author 晓风残月Lx
* @date 2023/4/12 14:46
*/
public enum SearchEnum {
/**
* mysql 搜索
*/
MYSQL(0, "mysql搜索", "mysqlStrategyImpl"),
/**
* elasticsearch搜索
*/
ELASTICSEARCH(1, "elasticsearch搜索", "elasticsearchStrategyImpl");
private final int type;
private final String desc;
private final String strategy;
public int getType() {
return type;
}
public String getDesc() {
return desc;
}
public String getStrategy() {
return strategy;
}
SearchEnum(int type, String desc, String strategy) {
this.type = type;
this.desc = desc;
this.strategy = strategy;
}
public static String getStrategy(int type) {
for (SearchEnum value : SearchEnum.values()) {
if (value.getType() == type) {
return value.getStrategy();
}
}
return null;
}
}
import com.xfcy.blog.vo.ArticleSearchVO;
import java.util.List;
/**
* 搜索策略
* @author 晓风残月Lx
* @date 2023/4/12 14:59
*/
public interface SearchStrategy {
List<ArticleSearchVO> searchArticle(String keywords);
}
SearchStrategyContext 类中自动注入Map
import com.xfcy.blog.strategy.SearchStrategy;
import com.xfcy.blog.vo.ArticleSearchVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 搜索策略上下文
* @author 晓风残月Lx
* @date 2023/4/12 14:57
*/
@Service
public class SearchStrategyContext {
@Autowired
private Map<String, SearchStrategy> searchStrategyMap = new ConcurrentHashMap<>();
/**
* 执行搜索策略
* @param keywords 关键字
* @return {@link List < ArticleSearchVO >} 搜索文章
*/
public List<ArticleSearchVO> executeSearchStrategy(String searchMode, String keywords) {
return searchStrategyMap.get(searchMode).searchArticle(keywords);
}
}
后面就是Mysql和ElasticSearch的代码,主要介绍es
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xfcy.blog.common.constant.Constant;
import com.xfcy.blog.common.enums.PublishEnum;
import com.xfcy.blog.entity.Article;
import com.xfcy.blog.mapper.ArticleMapper;
import com.xfcy.blog.strategy.SearchStrategy;
import com.xfcy.blog.vo.ArticleSearchVO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author 晓风残月Lx
* @date 2023/4/12 15:32
*/
@Service("mysqlStrategyImpl")
public class MysqlSearchStrategyImpl implements SearchStrategy {
@Resource
private ArticleMapper articleMapper;
/**
* 搜索功能搜索文章和标题
* @param keywords
* @return
*/
@Override
public List<ArticleSearchVO> searchArticle(String keywords) {
// 搜索文章 (简介和标题模糊查询)
List<Article> articles = articleMapper.selectList(new LambdaQueryWrapper<Article>()
.eq(Article::getIsPublish, PublishEnum.PUBLISH.getCode())
.and(i -> i.like(Article::getTitle, keywords)
.or()
.like(Article::getSummary, keywords))
.orderByDesc(Article::getIsStick, Article::getCreateTime));
// 高亮处理
List<ArticleSearchVO> articleSearchVOList = articles.stream().map(item -> {
// 获取关键词第一次出现的位置
String articleSummary = item.getSummary();
int index = item.getSummary().indexOf(keywords);
if (index != -1) {
// 文章简介高亮
articleSummary = articleSummary.replaceAll(keywords, Constant.START_TAG + keywords + Constant.LAST_TAG);
}
// 文章标题高亮
String articleTitle = item.getTitle().replaceAll(keywords, Constant.START_TAG + keywords + Constant.LAST_TAG);
return new ArticleSearchVO(item.getId(), articleTitle, articleSummary);
}).collect(Collectors.toList());
return articleSearchVOList;
}
}
ElasticSearch的版本是: 7.17.9
kibana的版本是:7.17.9
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
elasticsearch:
rest:
uris: 8.130.35.134:9200
import com.xfcy.blog.service.ElasticSearchService;
import com.xfcy.blog.strategy.SearchStrategy;
import com.xfcy.blog.vo.ArticleSearchVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @author 晓风残月Lx
* @date 2023/4/12 15:26
*/
@Service("elasticsearchStrategyImpl")
public class EsSearchStrategyImpl implements SearchStrategy {
@Resource
private ElasticSearchService elasticSearchService;
/**
* 根据关键词查简介和标题
* @param keywords
* @return
*/
@Override
public List<ArticleSearchVO> searchArticle(String keywords) {
return elasticSearchService.searchArticleByTitleAndSummary(keywords);
}
}
import com.xfcy.blog.entity.Article;
import com.xfcy.blog.vo.ArticleSearchVO;
import java.util.List;
/**
* ElasticSearch的 service类
* @author 晓风残月Lx
* @date 2023/4/24 19:54
*/
public interface ElasticSearchService {
/**
* 根据关键字搜索文章的标题和简介
* @param keywords
* @return
*/
List<ArticleSearchVO> searchArticleByTitleAndSummary(String keywords);
/**
* 新增
* @param article
*/
void addArticleSearchVO(Article article);
/**
* 修改
* @param article
*/
void updateArticleSearchVO(Article article);
/**
* 批量删除数据
* @param ids
*/
void deleteBatch(List<Long> ids);
}
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.xfcy.blog.common.constant.Constant;
import com.xfcy.blog.common.constant.SqlConstant;
import com.xfcy.blog.entity.Article;
import com.xfcy.blog.service.ElasticSearchService;
import com.xfcy.blog.strategy.impl.EsSearchStrategyImpl;
import com.xfcy.blog.utils.BeanCopyUtil;
import com.xfcy.blog.vo.ArticleSearchVO;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
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.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
* ElasticSearchService的实现类
* @author 晓风残月Lx
* @date 2023/4/24 19:54
*/
@Service("elasticSearchServiceImpl")
public class ElasticSearchServiceImpl implements ElasticSearchService {
private static final Logger logger = LoggerFactory.getLogger(EsSearchStrategyImpl.class);
@Resource
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 新增数据
* @param article
*/
@Override
@Async("threadPoolTaskExecutor")
public void addArticleSearchVO(Article article) {
long time = System.currentTimeMillis();
ArticleSearchVO articleSearchVO = BeanCopyUtil.copyObject(article, ArticleSearchVO.class);
elasticsearchRestTemplate.save(articleSearchVO);
logger.info("es新增文章,耗时:"+(System.currentTimeMillis() - time));
}
@Override
@Async("threadPoolTaskExecutor")
public void updateArticleSearchVO(Article article) {
long time = System.currentTimeMillis();
ArticleSearchVO articleSearchVO = BeanCopyUtil.copyObject(article, ArticleSearchVO.class);
String obj = JSONObject.toJSONString(articleSearchVO);
Document document = Document.parse(obj);
UpdateQuery updateQuery = UpdateQuery.builder(String.valueOf(articleSearchVO.getId()))
.withDocument(document).build();
IndexCoordinates indexCoordinatesFor = elasticsearchRestTemplate.getIndexCoordinatesFor(ArticleSearchVO.class);
elasticsearchRestTemplate.update(updateQuery, indexCoordinatesFor);
logger.info("es修改文章内容,耗时:"+(System.currentTimeMillis() - time));
}
/**
* 批量删除数据
* @param ids
*/
@Override
@Async("threadPoolTaskExecutor")
public void deleteBatch(List<Long> ids) {
ids.forEach(id -> elasticsearchRestTemplate.delete(id.toString(), ArticleSearchVO.class));
}
/**
* 根据关键词查简介和标题
*
* @param keywords
* @return
*/
public List<ArticleSearchVO> searchArticleByTitleAndSummary(String keywords) {
if (StringUtils.isBlank(keywords)) {
return new ArrayList<>();
}
return search(buildQuery(keywords));
}
/**
* 搜索文章构造
*
* @param keywords
* @return
*/
private NativeSearchQueryBuilder buildQuery(String keywords) {
// 条件构造器
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 根据关键词搜索文章标题或内容
boolQueryBuilder.must(QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery(SqlConstant.TITLE, keywords)))
.should(QueryBuilders.matchQuery(SqlConstant.SUMMARY, keywords));
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
return nativeSearchQueryBuilder;
}
/**
* 文章搜索结果高亮
* @param nativeSearchQueryBuilder
* @return
*/
private List<ArticleSearchVO> search(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
// 添加文章标题高亮
HighlightBuilder.Field titleField = new HighlightBuilder.Field(SqlConstant.TITLE);
titleField.preTags(Constant.START_TAG);
titleField.postTags(Constant.LAST_TAG);
// 添加文章内容高亮
HighlightBuilder.Field summaryField = new HighlightBuilder.Field(SqlConstant.SUMMARY);
summaryField.preTags(Constant.START_TAG);
summaryField.postTags(Constant.LAST_TAG);
summaryField.fragmentSize(200);
nativeSearchQueryBuilder.withHighlightFields(titleField, summaryField);
try{
SearchHits<ArticleSearchVO> search = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), ArticleSearchVO.class);
return search.getSearchHits().stream().map(hit -> {
ArticleSearchVO article = hit.getContent();
// 获取文章标题高亮数据
List<String> titleHighLightList = hit.getHighlightFields().get(SqlConstant.TITLE);
if (CollectionUtils.isNotEmpty(titleHighLightList)) {
// 替换标题数据
article.setTitle(titleHighLightList.get(titleHighLightList.size() - 1));
}
// 获取文章简介高亮数据
List<String> summaryHighLightList = hit.getHighlightFields().get(SqlConstant.SUMMARY);
if (CollectionUtils.isNotEmpty(summaryHighLightList)) {
// 替换简介数据
article.setSummary(summaryHighLightList.get(summaryHighLightList.size() - 1));
}
return article;
}).collect(Collectors.toList());
}catch (Exception e) {
logger.error(e.getMessage());
}
return new ArrayList<>();
}
}