网上找了很多方案,大多数都是实现去重数量查询,没有实现总数据去重查询功能,最后找到一篇文章是命令行去重相关的功能 参考文章
另外,找了很久搜索建议实现,全是使用前缀进行搜索,这里使用另外一种取巧方式进行实现,并且和数据近乎实时
温馨提示:本文主要做三件事
- 1、使用泛型保存搜索数据信息--通用性
- 2、保存搜索数据同时,进行搜索数据建议存储
- 3、搜索建议数据去重(也就是搜索数据)
一 、基础配置信息
- 1.1、pom文件中引入
1.8
UTF-8
UTF-8
7.7.0
1.2.70
1.8
org.projectlombok
lombok
true
org.elasticsearch.client
elasticsearch-rest-high-level-client
${elasticsearch.version}
org.elasticsearch
elasticsearch
${elasticsearch.version}
org.elasticsearch.client
elasticsearch-rest-client
${elasticsearch.version}
- 1.2、配置文件
本项目配置文件类型为 properties
spring.elasticsearch.rest.uris=http://127.0.0.1:9200
spring.elasticsearch.rest.username=es
spring.elasticsearch.rest.password=es
二、保存数据(同步数据到搜索引擎),字段用ik分词
- 2.1、保存搜索数据泛型
@Data
public class EsBatchAddReq {
/**
* 系统编码
*/
@NotBlank(message = "系统编码不能为空")
private String systemCode;
/**
* 业务编码,类似表名
*/
@NotBlank(message = "业务编码不能为空")
private String businessCode;
/**
* 保存的搜索数据列表信息,也就是从业务端同步过来数据信息
*
*/
@NotEmpty(message = "搜索数据列表不能为空")
private List beanList;
/**
* 搜索建议的数据字段信息, 必须为上面的 (T)某一个字段
*
*/
private List suggestEntities;
/**
* 此处生成的是ES的索引名称,系统编码和业务编码都必须是小写,有业务端自行定义
*
* @return
*/
public String getEsCode() {
return new StringBuilder(systemCode).append("_").append(businessCode).toString();
}
/**
* 此处生成的是业务端自定义搜索建议的索引名称,前缀以suggest_开头
*
* @return
*/
public String getSuggestCode() {
return new StringBuilder("suggest_").append(systemCode).append("_").append(businessCode).toString();
}
- 2.2、保存数据核心代码
public EsResponse upsetIndex(EsBatchAddReq requestParam) throws IOException {
// 判断开启数字嗅探 并且判断是否需要创建自定义mapping,并且创建索引
openNumber(requestParam);
BulkRequest request = new BulkRequest();
// 设置索引名称,索引名称由系统编码和业务编码组成。若业务比较简单,也可以仅设置为一个字段。此处是为了方便多系统多业务使用
request.routing(requestParam.getEsCode());
for (Object o : requestParam.getBeanList()) {
JSONObject jsonObject = (JSONObject) JSON.toJSON(o);
String id = jsonObject.getString(EsConstant.ES_ID);
Assert.notNull(id, "实体类必须包含id");
String data = JSONObject.toJSONString(o);
// 实体类必须包含ES自定义主键,搜索引擎ES将根据自定义ID进行重复新验证。
// 如果ID重复则修改数据,若ID不重复则新增数据
request.add(new IndexRequest(requestParam.getEsCode()).id(id).source(data, XContentType.JSON));
}
BulkResponse bulk = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
RestStatus status = bulk.status();
return EsResponse.successRes(status.toString());
}
/**
* 判断索引是否存在
* 新增判断搜索建议索引是否存在
* @param param
*/
// 此处命名太丑了 ,,,凑合用吧。。emmm
private void openNumber(EsBatchAddReq param) throws IOException {
// 创建数据存储索引
GetIndexRequest indexRequest = new GetIndexRequest(param.getEsCode());
boolean exists = restHighLevelClient.indices().exists(indexRequest, RequestOptions.DEFAULT);
if (!exists) {
createMapping(param.getEsCode(), param.getIkParticiple());
}
// 如果搜索建议字段为空 不创建搜索建议
if (CollectionUtils.isEmpty(param.getSuggestEntities())){
return;
}
// 创建搜索建议索引
GetIndexRequest suggestIndex = new GetIndexRequest(param.getSuggestCode());
boolean suggestExit = restHighLevelClient.indices().exists(suggestIndex, RequestOptions.DEFAULT);
if (!suggestExit) {
// 搜索建议为了便于搜索提示,不使用IK分词,直接使用默认分词
createMapping(param.getSuggestCode(), Boolean.FALSE);
}
}
/**
* 创建索引
* @param esCode
* @param ikParticiple
* @throws IOException
*/
private void createMapping(String esCode, Boolean ikParticiple) throws IOException {
JSONObject jsonObject = new JSONObject();
Map mappingMap = new HashMap<>();
// 开启数字嗅探
mappingMap.put("numeric_detection", Boolean.TRUE);
// 设置动态时间映射
List dateList = new ArrayList<>();
dateList.add("MM/dd/yyyy");
dateList.add("yyyy/MM/dd HH:mm:ss");
dateList.add("yyyy-MM-dd");
dateList.add("yyyy-MM-dd HH:mm:ss");
mappingMap.put("dynamic_date_formats", dateList);
// 判断是否创建映射,如果没有则创建映射.同时必须要使用IK分词才会手动创建映射
if (ikParticiple) {
Map childMap = new HashMap<>();
childMap.put("analysis.analyzer.default.type", "ik_max_word");
Map map = new HashMap<>();
map.put("index", childMap);
// 设置分词器为IK 分词
jsonObject.put("settings", map);
}
jsonObject.put("mapping", mappingMap);
CreateIndexRequest createIndexRequest = new CreateIndexRequest(esCode).source(jsonObject.toJSONString(), XContentType.JSON);
restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
}
- 2.3、搜索建议数据保存
在保存搜索数据的controller 新增一个 sevice 异步保存搜索建议
@Autowired
private EsLogic esLogic;
@Autowired
private SuggestLogic suggestLogic;
/**
* 批量新增或者修改的功能
*
* @param requestParam
* @return
* @throws IOException
*/
@RequestMapping("/esSearch/upset")
public EsResponse upsetIndex(@RequestBody @Valid EsBatchAddReq requestParam) throws IOException {
// 1、保存搜索数据并且根据参数判断是否创建搜索建议接口
EsResponse esResponse = esLogic.upsetIndex(requestParam);
// 2、保存搜索建议数据信息
suggestLogic.upsetSuggest(requestParam);
return esResponse;
}
搜索建议保存核心代码
@Component
@Slf4j
public class SuggestLogic {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 异步新增搜索建议
*
* @param requestParam
*/
@Async
public void upsetSuggest(EsBatchAddReq requestParam) throws IOException {
if (CollectionUtils.isEmpty(requestParam.getSuggestEntities())) {
return;
}
log.info("开始异步执行搜索建议处理:{}, {}", requestParam.getSuggestCode(), JSONObject.toJSONString(requestParam.getSuggestEntities()));
List esSuggestEntities = new ArrayList<>(1024);
List fieldNames = requestParam.getSuggestEntities();
// 避免在循环中重复计算大小
int size = fieldNames.size();
List beanList = requestParam.getBeanList();
// 外层循环遍历实体类
for (Object o : beanList) {
JSONObject jsonObject = (JSONObject) JSON.toJSON(o);
// 实体类唯一主键
String id = jsonObject.getString(EsConstant.ES_ID);
// 搜索建议定义了一个删除标识字段,为了便于搜索时过滤掉已删除数据,如果不需要可以忽略
String deleteFlag = jsonObject.getString(“deleteFlag”);
// 取出每一个搜索建议字段的字段名称和字段值,
for (int i = 0; i < size; i++) {
EsSuggestEntity esSuggestEntity = new EsSuggestEntity();
String fieldName = fieldNames.get(i);
// 搜索建议的唯一主键信息用实体类id加上字段信息表示,此时也能保证唯一性
String newId = new StringBuilder(id).append("_").append(fieldName).toString();
esSuggestEntity.setId(newId);
esSuggestEntity.setDeleteFlag(deleteFlag);
String suggestValue = jsonObject.getString(fieldName);
esSuggestEntity.setSuggestValue(suggestValue);
if (!StringUtils.isEmpty(esSuggestEntity.getSuggestValue())) {
esSuggestEntities.add(esSuggestEntity);
}
}
}
BulkRequest request = new BulkRequest();
// 保存搜索建议字段
for (EsSuggestEntity o : esSuggestEntities) {
JSONObject jsonObject = (JSONObject) JSON.toJSON(o);
String id = jsonObject.getString(EsConstant.ES_ID);
String data = JSONObject.toJSONString(o);
// 如果ID重复则修改数据,若ID不重复则新增数据
request.add(new IndexRequest(requestParam.getSuggestCode()).id(id).source(data, XContentType.JSON));
}
request.routing(requestParam.getSuggestCode());
// 批量插入搜索建议
restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
}
}
搜索建议的实体类
@Data
public class EsSuggestEntity {
/**
* 唯一主键,取自搜索数据的唯一主键加上搜索建议字段名称
*/
private String id;
/**
* 字段名称
*/
private String suggestValue;
/**
* 删除标识
*/
private String deleteFlag;
}
三、数据搜索功能,权重搜索
此处详细搜索功能参考我另外一篇文章ES 7.x (ElasticSearch) 与Java集成使用新增、修改、查询含操作语句,文章已经更新权重搜索等,本文不在占用其他篇幅去说明
四、搜索建议。去重搜索
本文实现搜索建议分词搜索功能,但是没有实现拼音功能,而且还是使用取巧的方式实现的,如果大家有更好的方式实现,欢迎告知一下,此处仅为记录一种实现个人搜索建议功能代码逻辑,不喜勿喷,但欢迎多多提意见#
本文搜索建议使用是基于本文 2.2章节以及2.3的前提下。
- 搜索建议请求实体类
@Data
public class EsSuggestReq {
/**
* 业务系统编码
*/
@NotBlank(message = "系统编码不能为空")
private String systemCode;
/**
* 业务编码,类似表名
*/
@NotBlank(message = "业务编码不能为空")
private String businessCode;
/**
* 第几页
*/
private Integer page = 1;
/**
* 每页展示的梳理
*/
private Integer size = 10;
/**
* 从低几条开始(不传)
*/
private Integer from;
/**
* 搜索值
*/
private String searchValue;
/**
* 数据删除标识状态
*/
private String deleteFlag;
/**
* 此处生成的是ES的索引名称,请注意此处没有检验系统编码和业务编码大小写,只能是小写字母,也可以在此处设置转换为小写字母
*
* @return
*/
public String getSuggestCode() {
return new StringBuilder("suggest_").append(systemCode).append("_").append(businessCode).toString();
}
// 此处挖了个坑,个人认为没有必要查询第二页搜索建议数据,如果各位认为有必要请注意使用注释调的来计算
public Integer getFrom() {
// if (from == null){
// from = (page - 1) * size;
// }
from = 0;
return from;
}
}
搜索建议核心代码逻辑功能
/**
* 查询搜索建议
*
* @param suggestReq
* @return
*/
public EsResponse querySuggest(EsSuggestReq suggestReq) throws IOException {
// 设置搜索建议索引
SearchRequest searchRequest = new SearchRequest(suggestReq.getSuggestCode());
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 设置搜索的值
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder(“suggestValue”, suggestReq.getSearchValue());
boolQueryBuilder.should(matchQueryBuilder);
// 设置删除功能, 搜索建议不应该把过期的数据给删除
if (!StringUtils.isEmpty(suggestReq.getDeleteFlag())){
boolQueryBuilder.must(QueryBuilders.termsQuery("deleteFlag.keyword", suggestReq.getDeleteFlag()));
}
searchSourceBuilder.query(boolQueryBuilder).from(suggestReq.getFrom()).size(suggestReq.getSize());
// 去重功能,此时我们的搜索字段的名称为 suggestValue ,此处需要使用 suggestValue.keyword 来进行查询
CollapseBuilder collapseBuilder = new CollapseBuilder("suggestValue.keyword");
searchSourceBuilder.collapse(collapseBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
EsResponse suggestPageResponse = EsResponseUtil.getSuggestPageResponse(search, suggestReq.getPage(), suggestReq.getSize());
return suggestPageResponse;
}
五、本文其他类代码信息
- 搜索数据处理工具类 方法
EsResponse esResponse;
Set resultSet = new HashSet<>(size * 2);
// 如果成功
if (HttpStatus.SC_OK == response.status().getStatus()) {
esResponse = new EsResponse();
SearchHit[] hits = response.getHits().getHits();
if (hits != null && hits.length != 0) {
for (SearchHit searchHit : hits) {
Map sourceAsMap = searchHit.getSourceAsMap();
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(sourceAsMap));
String suggestValue = jsonObject.getString("suggestValue");
resultSet.add(suggestValue);
}
}
esResponse.setData(JSONObject.toJSONString(resultSet));
} else {
esResponse = EsResponse.failRes(String.valueOf(response.status().getStatus()), response.status().toString());
}
return esResponse;
}
- 响应参数信息
public class EsResponse implements Serializable {
private static final long serialVersionUID = 14284224234985L;
/**
* 返回编码 0代表成功
*/
private String code = "0";
/**
* 返回参数信息
*/
private String message = "操作成功";
/**
* 是否成功
*/
private Boolean success = true;
/**
* 数据信息
*/
private String data;
/**
* 成功的方法
*
* @return
*/
public static EsResponse successRes() {
return new EsResponse();
}
/**
* 成功的方法
*
* @return
*/
public static EsResponse successRes(String data) {
EsResponse response = new EsResponse();
response.setData(data);
return response;
}
/**
* 失败的方法
*
* @return
*/
public static EsResponse failRes() {
EsResponse response = new EsResponse();
response.setSuccess(Boolean.FALSE);
response.setCode("999999");
response.setMessage("ES操作错误,请联系管理员");
return response;
}
/**
* 失败的方法
*
* @return
*/
public static EsResponse failRes(String retMsg) {
EsResponse response = new EsResponse();
response.setSuccess(Boolean.FALSE);
response.setCode("999999");
response.setMessage(retMsg);
return response;
}
/**
* 失败的方法
*
* @return
*/
public static EsResponse failRes(String retCode, String retMsg) {
EsResponse response = new EsResponse();
response.setSuccess(Boolean.FALSE);
response.setCode(retCode);
response.setMessage(retMsg);
return response;
}
}
本文结束,感谢大家的浏览,如果觉得本文对大家有帮助,欢迎点个赞,如果文章中有错误的地方,欢迎大家批评指正;
最后关于搜索建议这块,如果有更好的建议欢迎告知,蟹蟹!