用ES搜索关键字并且返回模糊字段高亮

   一般来说,各个网站,首页的搜索,都会有进行全文搜索的示例,并且把模糊匹配的多个数据进行标记(高亮),这样便于全局检索关键的数据,便于客户进行浏览。基于此,本文简单介绍这种功能基本java 的 实现

   由于公司页面此功能隐藏了,本文就以接口调用返回看具体实现了

   下面是最终的想要的结果,搜索内容代码 1, type 是 类型  
    类型ID: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同,

   每一种类型代表一张表的数据

控制层 controller 

@ApiOperation("关键字搜索结果页聚合")
@PostMapping("/pageListAgg")
public R pageListAgg(@RequestBody @Valid GlobalSearchDataReqDTO globalSearchDataReqDTO) {
 return success(globalSearchService.pageListAgg(globalSearchDataReqDTO));
}
逻辑层 service
public Map<String, Object> pageListAgg(GlobalSearchDataReqDTO globalSearchDataReqDTO) {
        Map<String, Object> resultMap = new HashMap<>();

        if (Objects.isNull(globalSearchDataReqDTO.getType())) {
            globalSearchDataReqDTO.setType(1);
        }
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // 构建查询条件 & 高亮
        this.searchConditionBuild(globalSearchDataReqDTO, nativeSearchQueryBuilder);
        // 排序
        nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort());
        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("type").order(SortOrder.ASC));
        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        // terms 指定分组的别名, field 指定要分组的字段名, size 指定查询结果的数量 默认是10个
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("typeGroup").field("type").size(10);
        // 分页
        if (Objects.isNull(globalSearchDataReqDTO.getType())) {
            globalSearchDataReqDTO.setType(1);
        }
        TopHitsAggregationBuilder topHitsAggregationBuilder = AggregationBuilders.topHits(globalSearchDataReqDTO.getType().toString());
        if (globalSearchDataReqDTO.getPageNum() >= 1) {
            topHitsAggregationBuilder.from((globalSearchDataReqDTO.getPageNum() - 1) * globalSearchDataReqDTO.getPageSize());
        }
        topHitsAggregationBuilder.size(globalSearchDataReqDTO.getPageSize());
        aggregation.subAggregation(topHitsAggregationBuilder);
        nativeSearchQueryBuilder.addAggregation(aggregation);
        // 构建并查询
        NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
        SearchHits<GlobalSearchData> search = elasticsearchTemplate.search(nativeSearchQuery, GlobalSearchData.class);

        // 聚合结果
        ParsedStringTerms typeAgg = Objects.requireNonNull(search.getAggregations()).get("typeGroup");
        Map<String, Object> typeMap = getStringsTypeList(typeAgg, globalSearchDataReqDTO.getType());

        List<GlobalSearchData> globalSearchDataList = JSONObject.parseArray(JSON.toJSONString(typeMap.get("dataList")), GlobalSearchData.class);
        List<GlobalSearchResultDTO> resultList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(globalSearchDataList)) {
            // 高亮
            List<SearchHit<GlobalSearchData>> searchHits = search.getSearchHits();
            List<GlobalSearchData> dataList = getHighLightData(searchHits, globalSearchDataList);
            // 返回数据处理
            resultList = globalSyncDataService.resultDataHandle(dataList, globalSearchDataReqDTO);
        }
        //resultMap.put("total", Objects.nonNull(typeMap.get("typeTotal")) ? typeMap.get("typeTotal") : 0); //总记录数
        resultMap.put("total", resultList.size()); //记录数
        resultMap.put("resultList", resultList); //结果数据
        typeMap.remove("typeTotal");
        typeMap.remove("dataList");
        resultMap.put("typeMap", typeMap); //聚合数据
        return resultMap;
    }

// 构建查询条件 & 高亮


private void searchConditionBuild(GlobalSearchDataReqDTO globalSearchDataReqDTO, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        String searchContent = globalSearchDataReqDTO.getSearchContent();
        builder.must(QueryBuilders.matchQuery("accountId", globalSearchDataReqDTO.getAccountId()));

        // 其他条件 模糊搜索 OR
        BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery();
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("customerName", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("contactName", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("companyName", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("title", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("code", searchContent).boost(5).slop(25));

        if (!judgeContainsStr(searchContent) && searchContent.length() < 13) {
            shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("mobile", searchContent).boost(5).slop(25));
            shouldBuilder.should(QueryBuilders.termQuery("phone", searchContent).boost(5));
            shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("phone", searchContent).boost(5).slop(25));
            shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("companyTelephone", searchContent).boost(5).slop(25));
        }
//        BoolQueryBuilder shouldBuilder1 = QueryBuilders.boolQuery();
//        shouldBuilder1.should(QueryBuilders.matchPhraseQuery("title", searchContent).slop(25));
//        builder.must(shouldBuilder1);
        builder.must(shouldBuilder);


        if (!tokenManager.getBase().isMain()) {
            // 当前登录人userId在 canSeeUserId中 或者 canSeeUserId = 1
            BoolQueryBuilder userIdShouldBuild = QueryBuilders.boolQuery();
            userIdShouldBuild.should(QueryBuilders.termQuery("canSeeUserId", globalSearchDataReqDTO.getUserId()));
            userIdShouldBuild.should(QueryBuilders.termQuery("canSeeUserId", -1));
            builder.must(userIdShouldBuild);
        }
        nativeSearchQueryBuilder.withFilter(builder);
        nativeSearchQueryBuilder.withQuery(builder);

        //设置高亮的字段
        nativeSearchQueryBuilder.withHighlightFields(
                new HighlightBuilder.Field("customerName"),
                new HighlightBuilder.Field("contactName"),
                new HighlightBuilder.Field("companyName"),
                new HighlightBuilder.Field("title"),
                new HighlightBuilder.Field("code"),
                new HighlightBuilder.Field("mobile"),
                new HighlightBuilder.Field("phone"),
                new HighlightBuilder.Field("companyTelephone")
        );

        nativeSearchQueryBuilder.withHighlightBuilder(new HighlightBuilder()
                .preTags("<em style=\"color:#165DFF\">").postTags("</em>")
                .fragmentSize(800000) //最大高亮分片数
                .numOfFragments(0)); //从第一个分片获取高亮片段
    }
获取分组结果

  private Map<String, Object> getStringsTypeList(ParsedStringTerms typeAgg, Integer type) {
        Map<String, Object> retMap = new HashMap<>();
        if (Objects.nonNull(typeAgg)) {
            List<? extends Terms.Bucket> buckets = typeAgg.getBuckets();
            buckets.forEach(bucket -> {
                if (type.toString().equals(bucket.getKeyAsString())) {
                    retMap.put("typeTotal", bucket.getDocCount());
                }
                retMap.put(bucket.getKeyAsString(), bucket.getDocCount() > 2000 ? 2000 : bucket.getDocCount()); //记录数 最大限制2000条

                if (type.toString().equals(bucket.getKeyAsString())) {
                    ParsedTopHits topHits = bucket.getAggregations().get(bucket.getKeyAsString());
                    if (Objects.nonNull(topHits.getHits())) {
                        org.elasticsearch.search.SearchHit[] hits = topHits.getHits().getHits();
                        List<GlobalSearchData> dataList = Lists.newArrayList();
                        for (org.elasticsearch.search.SearchHit hit : hits) {
                            dataList.add(JSONObject.parseObject(JSONObject.toJSONString(hit.getSourceAsMap()), GlobalSearchData.class));
                        }
                        retMap.put("dataList", dataList);
                    }
                }
            });
        }

        return retMap;
    }

处理结果返回字段
 

public List<GlobalSearchResultDTO> resultDataHandle(List<GlobalSearchData> globalSearchDataList, GlobalSearchDataReqDTO globalSearchDataReqDTO) {
        Long accountId = globalSearchDataReqDTO.getAccountId();
        List<GlobalSearchResultDTO> list = Lists.newArrayList();
        if (CollectionUtils.isEmpty(globalSearchDataList)) {
            return list;
        }
        List<Long> ids = globalSearchDataList.stream().map(GlobalSearchData::getSystemId).collect(Collectors.toList());

        switch (globalSearchDataReqDTO.getType()) {
            case ESGlobalTypeConstant.LEAD:
                list = leadsMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setStatusName(Objects.isNull(item.getStatus()) ? ConstantsEnum.LeadsStatus.FOLLOW_UP.title : ConstantsEnum.LeadsStatus.getTitle(item.getStatus())));
                break;
            case ESGlobalTypeConstant.CUSTOMER:
                list = customerMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setLevel(dictionaryService.getTitleByTypeAndCode(DictTypeEnum.CUSTOMER_LEVEL, Optional.ofNullable(item.getLevel()).map(Object::toString).orElse(""))));
                break;
            case ESGlobalTypeConstant.BUSINESS:
                list = businessMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> {
                    item.setStatusName(String.valueOf(BusinessStatusEnum.getDesc(item.getStatus().byteValue())));
                });
                break;
            case ESGlobalTypeConstant.CONTACT:
                list = contactMapper.selectGlobalResultData(accountId, ids);
                break;
            case ESGlobalTypeConstant.PRESALE:
                list = presaleMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> {
                    item.setStatusName(PresaleStatusEnum.getByCode(item.getStatus()).getDesc());
                    if (StringUtils.equals(item.getStatusName(), PresaleStatusEnum.PENDING_APPROVAL.getDesc())) {
                        item.setPresaleStage("--");
                        return;
                    }
                    if (StringUtils.isBlank(item.getPresaleStage())) {
                        item.setPresaleStage(presaleStageService.getFirstStageName(accountId, item.getSystemId()));
                    }
                });
                break;
            case ESGlobalTypeConstant.ORDER:
                list = orderMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setStatusName(String.valueOf(OrderStatusEnum.getDesc(item.getStatus().byteValue()))));
                break;
            case ESGlobalTypeConstant.CONTRACT:
                list = contractMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setStatusName(ConstantsEnum.ContractStatus.getTitle(item.getStatus())));
                break;
        }
        return listCover(globalSearchDataList, list, globalSearchDataReqDTO.getSearchContent());
    }
   private List<GlobalSearchData> getHighLightData(List<SearchHit<GlobalSearchData>> searchHits, List<GlobalSearchData> dataList) {
        List<GlobalSearchData> globalSearchDataList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(dataList)) {
            List<Long> ids = dataList.stream().map(GlobalSearchData::getId).collect(Collectors.toList());
            for (SearchHit<GlobalSearchData> searchHit : searchHits) {
                if (ids.contains(searchHit.getContent().getId())) {
                    highLightHandle(searchHit, globalSearchDataList);
                }
            }
        } else {
            for (SearchHit<GlobalSearchData> searchHit : searchHits) {
                highLightHandle(searchHit, globalSearchDataList);
            }
        }
        return globalSearchDataList;
    }
 private void highLightHandle(SearchHit<GlobalSearchData> searchHit, List<GlobalSearchData> globalSearchDataList) {
        //高亮的内容
        Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
        //将高亮的内容填充到content中
        searchHit.getContent().setCustomerName(highlightFields.get("customerName") == null ? searchHit.getContent().getCustomerName() : highlightFields.get("customerName").get(0));
        searchHit.getContent().setContactName(highlightFields.get("contactName") == null ? searchHit.getContent().getContactName() : highlightFields.get("contactName").get(0));
        searchHit.getContent().setCompanyName(highlightFields.get("companyName") == null ? searchHit.getContent().getCompanyName() : highlightFields.get("companyName").get(0));
        searchHit.getContent().setTitle(highlightFields.get("title") == null ? searchHit.getContent().getTitle() : highlightFields.get("title").get(0));
        searchHit.getContent().setCode(highlightFields.get("code") == null ? searchHit.getContent().getCode() : highlightFields.get("code").get(0));
        searchHit.getContent().setMobile(highlightFields.get("mobile") == null ? searchHit.getContent().getMobile() : highlightFields.get("mobile").get(0));
        searchHit.getContent().setPhone(highlightFields.get("phone") == null ? searchHit.getContent().getPhone() : highlightFields.get("phone").get(0));
        searchHit.getContent().setCompanyTelephone(highlightFields.get("companyTelephone") == null ? searchHit.getContent().getCompanyTelephone() : highlightFields.get("companyTelephone").get(0));
        globalSearchDataList.add(searchHit.getContent());
    }

入参
 

@Data
public class GlobalSearchDataReqDTO extends BasePage {

    /**
     * 搜索内容
     */
    //@NotNull(message = "搜索内容不能为空")
    private String searchContent;

    /**
     * 类型ID: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同
     */
    private Integer type;

    @ApiModelProperty(value = "归属人", hidden = true)
    private List<Long> ownerIds;

    @ApiModelProperty(value = "池归属", hidden = true)
    private List<Long> pools;

    @ApiModelProperty(value = "当前用户的部门", hidden = true)
    private List<Long> deptIds;

    @ApiModelProperty(value = "跟随客户的归属人", hidden = true)
    private List<Long> customerOwnerIds;

    @ApiModelProperty(value = "池管理员的池", hidden = true)
    private List<Long> adminPool;

    @ApiModelProperty(value = "池成员的池", hidden = true)
    private List<Long> partPool;
}


返回值参数
 

@Data
public class GlobalSearchResultDTO {

    @ApiModelProperty(value = "对应主键ID")
    private Long systemId;

    /**
     * 类型: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同
     */
    @ApiModelProperty(value = "类型")
    private Integer type;

    @ApiModelProperty(value = "类型名称")
    private String typeName;

    @ApiModelProperty(value = "标题")
    private String title;

    @ApiModelProperty(value = "编号")
    private String code;

    @ApiModelProperty(value = "客户id")
    private Long customerId;

    @ApiModelProperty(value = "客户名称")
    private String customerName;

    @ApiModelProperty(value = "联系人姓名")
    private String contactName;

    @ApiModelProperty(value = "公司名称")
    private String companyName;

    @ApiModelProperty(value = "联系人手机")
    private String mobile;

    @ApiModelProperty(value = "联系人电话")
    private String phone;

    @ApiModelProperty(value = "公司电话")
    private String companyTelephone;

    @ApiModelProperty(value = "池id")
    private Long poolId;

    @ApiModelProperty(value = "池名称")
    private String poolName;

    @ApiModelProperty(value = "状态")
    private Integer status;

    @ApiModelProperty(value = "状态名")
    private String statusName;

    @ApiModelProperty(value = "关联数据id")
    private Long relationId;

    @ApiModelProperty(value = "来源")
    private String source;

    /* 客户 */
//    @ApiModelProperty(value = "所在区域")
//    private String location;

    @ApiModelProperty("客户等级")
    private String level;

    /* 商机 */
//    @ApiModelProperty(value = "协销人员")
//    private Long assistUserId;
//
//    @ApiModelProperty(value = "协助人员,逗号分隔")
//    private String assistUser;

    @ApiModelProperty(value = "商机意向度")
    private String stageDesc;

    /* 售前 */
    @ApiModelProperty("售前负责人id")
    private Long presaleSalesmanId;

    @ApiModelProperty(value = "售前负责人")
    private String presaleSalesman;

    @ApiModelProperty(value = "售前阶段")
    private String presaleStage;

    /* 订单 */
//    @ApiModelProperty(value = "商品名称")
//    private String productName;

    @ApiModelProperty(value = "金额")
    private BigDecimal money;

    @ApiModelProperty("签订日期")
    private LocalDate signDate;

    @ApiModelProperty(value = "归属人姓名")
    private String ownerName;

    @ApiModelProperty(value = "数据创建人姓名")
    private String createByName;

    @ApiModelProperty(value = "创建时间")
    private String createTime;

    @ApiModelProperty(value = "联系人手机数组")
    private List<ContactReturn> mobileList;

    public void convertEmptyValue() {
        this.setTitle(StringUtils.originalOrEmpty(this.getTitle()));
        this.setCode(StringUtils.originalOrEmpty(this.getCode()));
        this.setCustomerName(StringUtils.originalOrEmpty(this.getCustomerName()));
        this.setContactName(StringUtils.originalOrEmpty(this.getContactName()));
        this.setCompanyName(StringUtils.originalOrEmpty(this.getCompanyName()));
        this.setPhone(StringUtils.originalOrEmpty(this.getPhone()));
        this.setMobile(StringUtils.originalOrEmpty(this.getMobile()));
        this.setCompanyTelephone(StringUtils.originalOrEmpty(this.getCompanyTelephone()));
        this.setPoolName(StringUtils.originalOrEmpty(this.getPoolName()));
        this.setStatusName(StringUtils.originalOrEmpty(this.getStatusName()));
        this.setSource(StringUtils.originalOrEmpty(this.getSource()));
        this.setLevel(StringUtils.originalOrEmpty(this.getLevel()));
        this.setStageDesc(StringUtils.originalOrEmpty(this.getStageDesc()));
        this.setPresaleSalesman(StringUtils.originalOrEmpty(this.getPresaleSalesman()));
        this.setPresaleStage(StringUtils.originalOrEmpty(this.getPresaleStage()));
        this.setOwnerName(StringUtils.originalOrEmpty(this.getOwnerName()));
        this.setCreateByName(StringUtils.originalOrEmpty(this.getCreateByName()));
    }


}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值