一般来说,各个网站,首页的搜索,都会有进行全文搜索的示例,并且把模糊匹配的多个数据进行标记(高亮),这样便于全局检索关键的数据,便于客户进行浏览。基于此,本文简单介绍这种功能基本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 MappageListAgg(GlobalSearchDataReqDTO globalSearchDataReqDTO) { Map 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 search = elasticsearchTemplate.search(nativeSearchQuery, GlobalSearchData.class); // 聚合结果 ParsedStringTerms typeAgg = Objects.requireNonNull(search.getAggregations()).get("typeGroup"); Map typeMap = getStringsTypeList(typeAgg, globalSearchDataReqDTO.getType()); List globalSearchDataList = JSONObject.parseArray(JSON.toJSONString(typeMap.get("dataList")), GlobalSearchData.class); List resultList = Lists.newArrayList(); if (CollectionUtils.isNotEmpty(globalSearchDataList)) { // 高亮 List > searchHits = search.getSearchHits(); List 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("").postTags("") .fragmentSize(800000) //最大高亮分片数 .numOfFragments(0)); //从第一个分片获取高亮片段 }
获取分组结果
private MapgetStringsTypeList(ParsedStringTerms typeAgg, Integer type) { Map 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 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 ListresultDataHandle(List globalSearchDataList, GlobalSearchDataReqDTO globalSearchDataReqDTO) { Long accountId = globalSearchDataReqDTO.getAccountId(); List list = Lists.newArrayList(); if (CollectionUtils.isEmpty(globalSearchDataList)) { return list; } List 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 ListgetHighLightData(List > searchHits, List dataList) { List globalSearchDataList = Lists.newArrayList(); if (CollectionUtils.isNotEmpty(dataList)) { List ids = dataList.stream().map(GlobalSearchData::getId).collect(Collectors.toList()); for (SearchHit searchHit : searchHits) { if (ids.contains(searchHit.getContent().getId())) { highLightHandle(searchHit, globalSearchDataList); } } } else { for (SearchHit searchHit : searchHits) { highLightHandle(searchHit, globalSearchDataList); } } return globalSearchDataList; }
private void highLightHandle(SearchHitsearchHit, List globalSearchDataList) { //高亮的内容 Map > 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()); }
入参
@Datapublic class GlobalSearchDataReqDTO extends BasePage { //@NotNull(message = "搜索内容不能为空") private String searchContent; private Integer type; @ApiModelProperty(value = "归属人", hidden = true) private ListownerIds; @ApiModelProperty(value = "池归属", hidden = true) private List pools; @ApiModelProperty(value = "当前用户的部门", hidden = true) private List deptIds; @ApiModelProperty(value = "跟随客户的归属人", hidden = true) private List customerOwnerIds; @ApiModelProperty(value = "池管理员的池", hidden = true) private List adminPool; @ApiModelProperty(value = "池成员的池", hidden = true) private List partPool;}
返回值参数
@Datapublic class GlobalSearchResultDTO { @ApiModelProperty(value = "对应主键ID") private Long systemId; @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 ListmobileList; 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())); }}