使用策略模式实现两种搜索Mysql和ElasticSearch7.17.x

运用策略模式来实现Mysql和ELasticSearch的搜索,根据数据库中查询出来的结果然后通过SearchEnum枚举类获取到项目中需要的策略模式(也就是mysqlStrategyImpl或者elasticsearchStrategyImpl)。

1.枚举类

/**
 * @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;
    }

}

2.搜索策略

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 searchStrategyMap,在初始化时,会将SearchStrategy接口的所有类对象放入Map集合中,在通过SearchStrategyContext类对象调用executeSearchStrategy,然后会通过传来的进行执行对应的类。

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);
    }


}

3.策略的接口实现类

后面就是Mysql和ElasticSearch的代码,主要介绍es

3.1 MysqlSearchStrategyImpl


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;
    }
}

3.2.EsSearchStrategyImpl

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<>();
    }

}


你可能感兴趣的:(Java开发总结,Java设计模式,策略模式,mysql,elasticsearch)