如何使用elasticsearch实现站内搜索并高亮显示 | mysql对elasticsearch做初始化

分析

分词倒排索引分析

在这里插入图片描述

高亮分析

在这里插入图片描述

搜索的接口

  • 此文章只做攻略接口的演示,其他雷同~
    在这里插入图片描述

初始化数据分析

在这里插入图片描述

效果图

  • 输入广州,将从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";

    //@Field 每个文档的字段配置(类型、是否分词、是否存储、分词器 )
    @Id
    @Field( index = false,type = FieldType.Long)
    private Long id;  //攻略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

  • 基本的crud
@Service
public class StrategyEsServiceImpl implements IStrategyEsService {

    @Autowired
    private StrategyEsRepository repository;

    @Override
    public void save(StrategyEs strategyEsEs) {
        //strategyEsEs.setId(null);
        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 {

//        URL解码
        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);
    }

业务方法实现类(重点学习拷贝)

  • 所有es公共服务接口,万能接口
/**
 * 所有es公共服务
 */
public interface ISearchService {

    /**
     * 全文搜索 + 高亮显示
     * @param index 索引
     * @param type  类型
     * @param clz
     * @param qo
     * @param fields
     * @param <T>
     * @return 带有分页的全文搜索(高亮显示)结果集
     */
    <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;

    /**
     * 全文搜索 + 高亮显示
     * @param index 索引
     * @param type  类型
     * @param clz
     * @param qo
     * @param fields
     * @param <T>
     * @return 带有分页的全文搜索(高亮显示)结果集
     */
    //关键字: keyword = 广州
    //以title为例:
    //原始匹配: "有娃必看,广州长隆野生动物园全攻略"
    //高亮显示后:"有娃必看,<span style="color:red;">广州</span>长隆野生动物园全攻略"
    @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);   // 设置搜索类型
        /*"query":{
            "multi_match": {
                "query": "广州",
                "fields": ["title","subTitle","summary"]
            }
        },*/
        searchQuery.withQuery(QueryBuilders.multiMatchQuery(qo.getKeyword(), fields));  //拼接查询条件
        /**
         "from": 0,
         "size":3,
         */
        Pageable pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize(),
                Sort.Direction.ASC, "_id");

        searchQuery.withPageable(pageable);   //分页操作

        //高亮显示
        /**
         "highlight": {
            "fields" : {
                 "title" : {},
                 "subTitle" : {},
                 "summary" : {}
            }
         }
         */
        String preTags = "<span style='color:red;'>";
        String postTags = "</span>";

        //需要进行高亮显示的字段对象, 他是一个数组, 个数由搜索的字段个数决定: fields 个数决定
        //fields : title subTitle  summary
        HighlightBuilder.Field[] fs = new HighlightBuilder.Field[fields.length];
        for(int i = 0; i < fs.length; i++){
            //最终查询结果: <span style="color:red;">广州</span>
            fs[i] = new HighlightBuilder.Field(fields[i])
                    .preTags(preTags)  //拼接高亮显示关键字的开始的样式   <span style="color:red;">
                    .postTags(postTags);//拼接高亮显示关键字的结束的样式   </span>
        }

        searchQuery.withHighlightFields(fs);

        //List<UserInfoEs> es = template.queryForList(searchQuery.build(), UserInfoEs.class);


        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(); //结果对象中hist 里面包含全文搜索结果集
                SearchHit[] searchHits = hits.getHits();//结果对象中hist的hit 里面包含全文搜索结果集
                for (SearchHit searchHit : searchHits) {
                    T t = mapSearchHit(searchHit, clazz);
                    //必须使用拥有高亮显示的效果字段替换原先的数据
                    //参数1: 原始对象(字段中没有高亮显示)
                    //参数2:具有高亮显示效果字段, 他是一个map集合, key: 高亮显示字段名, value: 高亮显示字段值对象
                    //参数3:需要替换所有字段
                    Map<String, String> map = highlightFieldsCopy(searchHit.getHighlightFields(), fields);
                    //BeanUtils.copyProperties(map, t);

                    //1:spring 框架中BeanUtils 类,如果是map集合是无法进行属性复制
                    //   copyProperties(源, 目标)
                    //2: apache  BeanUtils 类 可以进map集合属性复制
                    //   copyProperties(目标, 源)
                    try {
                        BeanUtils.copyProperties(t, map);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }

                    list.add(t);
                }

                //将结果集封装成分页对象Page : 参数1:查询数据, 参数2:分页数据, 参数3:查询到总记录数
                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;
            }
        });
    }


    //fields: title subTitle summary
    private Map<String, String>   highlightFieldsCopy(Map<String, HighlightField> map, String ...fields){
        Map<String, String> mm = new HashMap<>();
        //title:  "有娃必看,<span style='color:red;'>广州</span>长隆野生动物园全攻略"
        //subTitle: "<span style='color:red;'>广州</span>长隆野生动物园"
        //summary: "如果要说动物园,楼主强烈推荐带娃去<span style='color:red;'>广州</span>长隆野生动物园
        //title subTitle summary
        for (String field : fields) {

            HighlightField hf = map.get(field);
            if (hf != null) {
                //获取高亮显示字段值, 因为是一个数组, 所有使用string拼接
                Text[] fragments = hf.fragments();
                String str = "";
                for (Text text : fragments) {
                    str += text;
                }
                mm.put(field, str);  //使用map对象将所有能替换字段先缓存, 后续统一替换
                //BeanUtils.setProperty(t,field,  str);  识别一个替换一个
            }
        }
        return mm;
    }
}

mysql对elasticsearch初始化数据

  • 因为es实体类是通过mysql实体类分析而来的,所以做初始化数据,从mysql对象转化成es对象 并保存进es数据库中即可~
  • 为了方便演示,这里使用接口做初始化,也可以用监听器弄
  • 注入mysql和es的业务层对象,即可操作mysql和es数据库
  • 将mysql 的对象数据集合遍历,将里面每一个对象都转成es对象,然后存入es中
@RestController
public class DataController {

    //es服务
    @Autowired
    private IDestinationEsService destinationEsService;
    @Autowired
    private IStrategyEsService strategyEsService;
    @Autowired
    private ITravelEsService travelEsService;
    @Autowired
    private IUserInfoEsService userInfoEsService;

    //mysql服务
    @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";

    }

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值