【注意】这是个人笔记,希望对你有帮助,不喜勿喷!
知识补充:
arc
最慢但最精确的是 arc 计算方式,这种方式把世界当作球体来处理。不过这种方式的精度有限,因为这个世界并不是完全的球体。
plane
plane 计算方式把地球当成是平坦的,这种方式快一些但是精度略逊。在赤道附近的位置精度最好,而靠近两极则变差。
sloppy_arc
如此命名,是因为它使用了 Lucene 的 SloppyMath 类。这是一种用精度换取速度的计算方式, 它使用 Haversine formula 来计算距离。它比 arc 计算方式快 4 到 5 倍,并且距离精度达 99.9%。这也是默认的计算方式。
业务场景:
描述:ElasticSearch中查询出 Event 的信息,其他相关信息,比如该 Event 所对应的媒体信息等从数据库拿。
条件:必须满足 状态,类型,选择满足是否删除 或 该记录关联的用户id是否是当前登录用户
排序:先按照指定权重(list_order)倒叙,再按创建时间倒叙,最后按距离正序
基于 ElasticsearchRestTemplate 对象操作ES库
新版本的ES没有什么特殊的需求,不需要设置,直接注入即可。我使用的es-7.12.1,注意服务,和客户端的依赖本版尽量对应一致,否则容易出BUG。
啰嗦:个人亲身体会,es在不断的发展,在java中的操作每个版本都会有变化,隔几个版本变化就会特别大。所以这是为什么对依赖版本一致要求这么高。
@Resource
ElasticsearchRestTemplate elasticsearchRestTemplate;
实体:
记得补全getter&setter, 像这样创建实体之后,用save()方法,会自动创建ES索引
/**
* 用户动态Es版本实体
*/
@Document(indexName = "social_sys_events", shards = 3, replicas = 2)
public class EsEvent implements Serializable {
/**
* 用户动态ID
*/
@Id
@Field(name = "id", type = FieldType.Long)
private Long id;
/**
* 用户ID
*/
@Field(name = "user_id", type = FieldType.Long)
private Long userId;
/**
* 内容
*/
@Field(name = "content", type = FieldType.Text,analyzer = "ik_smart")
private String content;
/**
* 话题
*/
@Field(name = "topics", type = FieldType.Text, analyzer = "ik_smart")
private String topics;
/**
* 坐标集合
*/
@GeoPointField
@Field(name = "gs", type = FieldType.Auto)
private GeoPoint lonLat;
/**
* 地址
*/
@Field(name = "address", type = FieldType.Keyword)
private String address;
/**
* 点赞数
*/
@Field(name = "likes", type = FieldType.Integer)
private Long likes;
/**
* 评论数量
*/
@Field(name = "comments", type = FieldType.Integer)
private Long comments;
/**
* 消息类型 消息类型 0:普通图文贴;1:链接贴;2:二手车
*/
@Field(name = "type", type = FieldType.Short)
private Short type;
/**
* 二手车详情,此字段前端怎么存,我们怎么取
*/
@Field(name = "extension", type = FieldType.Keyword)
private String extension;
/**
* 悬赏金币
*/
@Field(name = "reward", type = FieldType.Keyword)
private Integer reward;
/**
* 状态 1 正常 0 待审核 2 禁用
*/
@Field(name = "status", type = FieldType.Integer)
private Integer status;
/**
* 删除
*/
@Field(name = "is_delete", type = FieldType.Long)
private Integer isDelete;
/**
* 排序
*/
@Field(name = "list_order", type = FieldType.Long)
private Long listOrder;
/**
* @ 对象
*/
@Field(name = "to_user_ids", type = FieldType.Keyword)
private String to_user_ids;
/**
* 备注
*/
@Field(name = "remark", type = FieldType.Text)
private String remark;
/**
* 创建时间
*/
@Field(name = "create_time", type = FieldType.Date)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@Field(name = "update_time", type = FieldType.Date)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
ES映射:
{
"social_sys_events" : {
"mappings" : {
"properties" : {
"address" : {
"type" : "keyword"
},
"comments" : {
"type" : "integer"
},
"content" : {
"type" : "text",
"analyzer" : "ik_smart"
},
"create_time" : {
"type" : "date",
"format" : "date_optional_time||epoch_millis"
},
"extension" : {
"type" : "keyword"
},
"gs" : {
"type" : "geo_point"
},
"id" : {
"type" : "keyword"
},
"is_delete" : {
"type" : "long"
},
"likes" : {
"type" : "integer"
},
"list_order" : {
"type" : "long"
},
"remark" : {
"type" : "text"
},
"reward" : {
"type" : "keyword"
},
"status" : {
"type" : "integer"
},
"to_user_ids" : {
"type" : "keyword"
},
"topics" : {
"type" : "text",
"analyzer" : "ik_smart"
},
"type" : {
"type" : "short"
},
"update_time" : {
"type" : "date",
"format" : "date_optional_time||epoch_millis"
},
"user_id" : {
"type" : "long"
}
}
}
}
}
EventServiceImpl,代码太长,只截了主要部分。
// 查询基础条件对象
BoolQueryBuilder baseBuilder = new BoolQueryBuilder();
// 构建符合 status 的条件
ArrayList<Object> objects = new ArrayList<>();
objects.add(CarConstant.NO);
objects.add(CarConstant.CAR_MOTOR);
// 如果用户没有车辆信息,那么默认应该查询所有
if (Objects.equals(carMessage.getData(), CarConstant.MOTOR)) {
objects.add(CarConstant.MOTOR);
} else if (Objects.equals(carMessage.getData(), CarConstant.CAR)) {
objects.add(CarConstant.CAR);
} else {
objects.add(CarConstant.MOTOR);
objects.add(CarConstant.CAR);
}
Object[] statuses = objects.toArray();
baseBuilder.must(QueryBuilders.termsQuery("status", statuses));
// 构建嵌套查询条件
BoolQueryBuilder multiBuilder = new BoolQueryBuilder();
// 没有被删除
multiBuilder.must(QueryBuilders.termQuery("is_delete", 0));
// 符合当前用户的ID
multiBuilder.should(QueryBuilders.termQuery("user_id", currentUser.getUserId()));
baseBuilder.must(multiBuilder);
// 如果type不为空
if (types != null && types.length > 0) {
// 构建type条件
baseBuilder.must(QueryBuilders.termsQuery("type", (Object[]) types));
}
// 按照距离排序
GeoDistanceSortBuilder distanceSortBuilder = SortBuilders.geoDistanceSort("gs", new GeoPoint(lat.doubleValue(), lng.doubleValue()))
.unit(DistanceUnit.KILOMETERS)
//.geoDistance(GeoDistance.ARC) // 距离计算方式, 具体详情知识补充部分。
.order(SortOrder.ASC);
// 构建查询对象
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(baseBuilder)
.withSort(SortBuilders.fieldSort("list_order").order(SortOrder.DESC)) // 根绝list_order倒序排列
.withSort(SortBuilders.fieldSort("create_time").order(SortOrder.DESC)) // 根据创建时间倒序排列
.withSort(distanceSortBuilder) // 根据距离正序排列
.withPageable(PageRequest.of(pageNum - 1, pageSize)) // 分页查询
.build();
// 开始查询
SearchHits<EsEvent> search = elasticsearchRestTemplate.search(query, EsEvent.class);
List<SearchHit<EsEvent>> searchHits = search.getSearchHits();
if (searchHits.size() == 0) {
return EsApiUtil.getUnifyResult(HttpStatus.ERROR, "没有查询到数据!");
}
ArrayList<Events> list = new ArrayList<>();
searchHits.forEach((x) -> {
// 此处的索引和查询返回结果中sort集合的索引一致,目的在于取返回结果中的距离计算结果,以免二次计算,造成资源浪费
Object geoDistance = x.getSortValues().get(2);
String distance = String.valueOf(Math.round((Double) geoDistance));
// 此方法贴在下方
Events event = esEventToEventHandler(x.getContent(), null, null, currentUser.getUserId());
event.setCurrentId(currentUser.getUserId());
event.setDistance(distance);
list.add(event);
});
esEventToEventHandler():
注释很详细,不赘述。
/**
* 将ElasticSearch中查询出来的数据EsEvent转换成Event对象,并且查询出该条记录的媒体信息、当前用户是否点过赞、与当前用户的距离等信息
*
* @param esEvent 需要转换的EsEvent对象
* @param lat 当前登录用户的经度
* @param lng 当前登录用户的纬度
* @param currentUserId 当前登录用户的ID
* @return 转换后的结果
*/
private Events esEventToEventHandler(EsEvent esEvent, BigDecimal lat, BigDecimal lng, Long currentUserId) {
Events event = new Events();
BeanUtils.copyProperties(esEvent, event);
// 距离问题
GeoPoint lonLat = esEvent.getLonLat();
event.setLat(BigDecimal.valueOf(lonLat.getLat()));
event.setLng(BigDecimal.valueOf(lonLat.getLon()));
// 当传进来的经纬度为空时,说明不需要计算距离
if (lat != null && lng != null){
double distance = GeoDistance.ARC.calculate(lat.doubleValue(), lng.doubleValue(), lonLat.getLat(), lonLat.getLon(), DistanceUnit.KILOMETERS);
// 四舍五入取近似数
event.setDistance(String.valueOf(Math.round(distance)));
}
// 查询动态的媒体信息
List<EventsMedia> eventsMediaList = eventsMediaMapper.selectEventsMediaList(event.getId());
if (eventsMediaList != null && eventsMediaList.size() > 0) {
event.setEventsMedia(eventsMediaList);
}
// 查询该动态当前用户是否点过赞
List<EventsLike> eventsLikeList = eventsLikeMapper.selectEventsLikeList(event.getId(), 0L, currentUserId);
event.setLike(eventsLikeList != null && eventsLikeList.size() > 0);
// 查询点赞总人数
int likesCount = eventsLikeMapper.selectEventsLikeCountsById(event.getId(), 0L);
event.setLikes(Long.parseLong(String.valueOf(likesCount)));
if (event.getDistance().equals("11354公里")) {
event.setDistance("");
}
Optional<EsBaseUser> optional = esBaseUserRepository.findById(String.valueOf(event.getUserId()));
// 发表该条动态的用户信息
if (optional.isPresent()) {
BaseUser baseUser = new BaseUser();
EsBaseUser esBaseUser = optional.get();
BeanUtils.copyProperties(esBaseUser, baseUser);
baseUser.setLng(BigDecimal.valueOf(esBaseUser.getLonLat().getLon()));
baseUser.setLat(BigDecimal.valueOf(esBaseUser.getLonLat().getLat()));
// 处理UserDetail的封面
if (StringUtils.isEmpty(baseUser.getCover())) {
baseUser.setCover(baseUser.getAvatar());
} else {
baseUser.setCover(OssUtils.GetThumb(baseUser.getCover(), "style_640_480"));
}
// 该条信息不应该出现在这些查询中
baseUser.setOid("");
event.setUser(baseUser);
}
return event;
}
ES脚本:
GET social_sys_events/_search
{
"query": {
"bool": {
"must": [
{"terms": {
"status": [
0,4,8
]
}},{
"bool": {
"must": [
{"term": {
"is_delete": {
"value": 0
}
}}
],
"should": [
{"term": {
"user_id": {
"value": "95792"
}
}}
]
}
},{
"terms": {
"type": [
0,1,2
]
}}
]
}
},
"sort": [
{
"list_order": {
"order": "desc"
},
"create_time": {
"order": "desc"
}
},{
"_geo_distance": {
"gs": {
"lat": 0,
"lon": 0
},
"order": "asc",
"unit": "km"
}
}
],
"from": 10,
"size": 10
}