背景
项目中遇到如下场景(简化描述):每家公司包含一个或多个实际经营网点,由每个网点承载实际的业务活动。因此公司自身的地理位置没有实际意义,每个网点的地理位置信息,具有实际意义。用户按照条件搜索出符合需要的公司,然后按照地理位置由近及远(相对于用户当前位置)进行排序。
使用ES
鉴于有其他复杂搜索条件,采用ElasticSearch实现项目的搜索要求。ES版本为5.5.0。
基于springboot,用spring-data-elasticsearch建立工程,具体过程不再赘述(参见:https://start.spring.io/)。IDEA自带这个功能,见下图:
初始思路
(1)ES中可以用geo_point类型代表位置信息,参见:geo_point类型说明,因此创建一个document,包含一个类型为GeoPoint的位置信息字段。工程里可以看到GeoPoint类,分别是spring-data-elasticsearch里的org.springframework.data.elasticsearch.core.geo.GeoPoint类和elasticSearch自带的org.elasticsearch.common.geo.GeoPoint类。两者功能类似,都是用经纬度来描述位置的,一个很大的区别在于后者提供了geoHash(由经纬度转化而来的字符串格式,参见:GeoHash原理)到GeoPoint的转化。此处用的spring提供的GeoPoint类。由于我们需要存储多个位置,所以用List<GeoPoint>作为位置的类型。
代码如下:
@Document(indexName = "companyindex", type = "companydata")
@Data
public class Company {
@Id
private Long companyId;
private List<GeoPoint> locations;
public Company(Long companyId) {
this.companyId = companyId;
}
}
此处indexName即为索引名,type为类型名,Company即为文档对象,类中定义的字段即为文档的field,@Id声明companyId为唯一id,添加文档时,即为每个文档的id。
索引文档方法代码如下:
@Service
public class IndexService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public void addDoc(Company company) {
IndexQuery indexQuery = new IndexQuery();
indexQuery.setObject(company);
elasticsearchTemplate.index(indexQuery);
}
}
测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
private IndexService indexService;
@Test
public void test() {
Company company1 = new Company(1L);
List<GeoPoint> locations1 = new ArrayList<>();
locations1.add(new GeoPoint(10.0000001, 20.0000001));
locations1.add(new GeoPoint(11.0000001, 21.0000001));
company1.setLocations(locations1);
indexService.addDoc(company1);
Company company2 = new Company(2L);
List<GeoPoint> locations2 = new ArrayList<>();
locations2.add(new GeoPoint(20.0000001, 30.0000001));
locations2.add(new GeoPoint(21.0000001, 31.0000001));
company2.setLocations(locations2);
indexService.addDoc(company2);
}
}
文档添加完成,通过Kibana查询全量数据