一般来说,各个网站,首页的搜索,都会有进行全文搜索的示例,并且把模糊匹配的多个数据进行标记(高亮),这样便于全局检索关键的数据,便于客户进行浏览。基于此,本文简单介绍这种功能基本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());
}
入参
文章来源:https://www.toymoban.com/news/detail-631569.html
@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;
}
返回值参数
文章来源地址https://www.toymoban.com/news/detail-631569.html
@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()));
}
}
到了这里,关于用ES搜索关键字并且返回模糊字段高亮的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!