Elasticsearch实现搜附近功能

Elasticsearch实现搜附近功能

1、搭建一个es集群,步骤如下

# 基于docker搭建
docker pull elasticsearch:6.5.4

# 单个进程中的最大线程数
vim /etc/sysctl.conf

# 在配置文件sysctl.conf加上下面的内容,wq保存
vm.max_map_count=262144

# 在虚拟机中输入如下指令,使其配置生效
/sbin/sysctl -p

# 创建文件夹
mkdir /root/es-cluster/node01 -p
mkdir /root/es-cluster/node02 -p
mkdir /root/es-cluster/node03 -p

# 分别在文件夹node01,node02,node03下面创建jvm.options
-Xms128m
-Xmx128m
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+AlwaysPreTouch
-Xss1m
-Djava.awt.headless=true
-Dfile.encoding=UTF-8
-Djna.nosys=true
-XX:-OmitStackTraceInFastThrow
-Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true
-Dio.netty.recycler.maxCapacityPerThread=0
-Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true
-Djava.io.tmpdir=${ES_TMPDIR}
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data
-XX:ErrorFile=logs/hs_err_pid%p.log
8:-XX:+PrintGCDetails
8:-XX:+PrintGCDateStamps
8:-XX:+PrintTenuringDistribution
8:-XX:+PrintGCApplicationStoppedTime
8:-Xloggc:logs/gc.log
8:-XX:+UseGCLogFileRotation
8:-XX:NumberOfGCLogFiles=32
8:-XX:GCLogFileSize=64m
9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m
9-:-Djava.locale.providers=COMPAT

# 分别在node01,node02,node03目录下,创建elasticsearch.yml文件,并输入如下内容(其他目录只需要改一下端口号和节点名称即可):
cluster.name: es-tanhua-cluster
node.name: node01
node.master: true 
node.data: true
network.host: 192.168.241.100
http.port: 9200
discovery.zen.ping.unicast.hosts: ["192.168.241.100"]
discovery.zen.minimum_master_nodes: 2
http.cors.enabled: true
http.cors.allow-origin: "*"

# node.master: true 具有被选举成master的权利
# discovery.zen.minimum_master_nodes: 2 最先获得两票的就是master
# node.data: true 用来存储数据

# 分别创建3个容器(其余的两个只需要更改名称和挂载目录名称即可)
docker create --restart=always --name es-node01 --net host -v /root/es-cluster/node01/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /root/es-cluster/node01/jvm.options:/usr/share/elasticsearch/config/jvm.options -v es-cluster-node01-data:/usr/share/elasticsearch/data elasticsearch:6.5.4

# 启动容器
docker start es-node01 es-node02 es-node03

# 查看日志
docker logs -f es-node01

# 单个启动并查看日志
docker start es-node01 && docker logs -f es-node01

2、搭建工程

导入依赖

 <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
</dependency>

编写application.properties

spring.application.name = tanhua-es
server.port = 18086

spring.data.elasticsearch.cluster-name=es-tanhua-cluster
spring.data.elasticsearch.cluster-nodes=192.168.241.100:9300,192.168.241.100:9301,192.168.241.100:9302

编写用户位置实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)
public class UserLocationEs{

    @Id
    private Long userId;
    
     /**
     * x:经度 y:纬度
     */
    @GeoPointField
    private GeoPoint location;

    @Field(type = FieldType.Keyword)
    private String address;

    @Field(type = FieldType.Long)
    private Long created;

    @Field(type = FieldType.Long)
    private Long updated;

    @Field(type = FieldType.Long)
    private Long lastUpdated;
}

// @Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)
// 索引的名字是tanhua(库) ;类型是user_location(表); shards = 6 分成六片 ; replicas = 2 副本为两个

控制层 UserLocationController

@RestController
@RequestMapping("es/user/location")
public class UserLocationController {

    @Autowired
    private UserLocationService userLocationService;

    /**
     * 查询用户的地理位置
     */
    @GetMapping("{userId}")
    public ResponseEntity<UserLocationEs> queryUserLocation(@PathVariable("userId") Long userId) {
        try {
            UserLocationEs userLocationEs = this.userLocationService.queryByUserId(userId);
            if (null == userLocationEs) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
            }
            return ResponseEntity.ok(userLocationEs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
    }

    /**
     * 更新用户的地理位置
     */
    @PostMapping
    public ResponseEntity<UserLocationEs> updateUserLocation(@RequestBody Map<String, Object> param) {
        try {
            Long userId = Long.valueOf(param.get("userId").toString());
            Double longitude = Double.valueOf(param.get("longitude").toString());
            Double latitude = Double.valueOf(param.get("latitude").toString());
            String address = param.get("address").toString();
            boolean result = this.userLocationService.updateUserLocation(userId, longitude, latitude, address);
            if (result) {
                return ResponseEntity.ok(null);
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
    }

    /**
     * 搜索附近的人
     */
    @PostMapping("list")
    public ResponseEntity<List<UserLocationEs>> queryUserFromLocation(@RequestBody Map<String, Object> param) {
        try {
            Double longitude = Double.valueOf(param.get("longitude").toString());
            Double latitude = Double.valueOf(param.get("latitude").toString());
            Double distance = Double.valueOf(param.get("distance").toString());
            Integer page = param.get("page") == null ? 1 : Integer.valueOf(param.get("page").toString());
            Integer pageSize = param.get("pageSize") == null ? 100 : Integer.valueOf(param.get("pageSize").toString());

            Page<UserLocationEs> userLocationEs = this.userLocationService.queryUserFromLocation(longitude, latitude, distance, page, pageSize);
            return ResponseEntity.ok(userLocationEs.getContent());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
    }
}

业务层

@Service
public class UserLocationService {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 更新用户的地理位置
     */
    public boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
        try {
            if (!this.elasticsearchTemplate.indexExists("tanhua")) {
                // (如果不存在tanhua数据库的话)创建索引
                this.elasticsearchTemplate.createIndex(UserLocationEs.class);
            }

            if (!this.elasticsearchTemplate.typeExists("tanhua", "user_location")) {
                // 创建type
                this.elasticsearchTemplate.putMapping(UserLocationEs.class);
            }

            GetQuery getQuery = new GetQuery();
            getQuery.setId(userId.toString());
            UserLocationEs ul = this.elasticsearchTemplate.queryForObject(getQuery, UserLocationEs.class);
            if (null == ul) {
            	// 没有的话,创建一个
                UserLocationEs userLocationEs = new UserLocationEs();

                userLocationEs.setLocation(new GeoPoint(latitude, longitude));
                userLocationEs.setAddress(address);
                userLocationEs.setUserId(userId);
                userLocationEs.setCreated(System.currentTimeMillis());
                userLocationEs.setUpdated(userLocationEs.getCreated());
                userLocationEs.setLastUpdated(userLocationEs.getCreated());

                IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocationEs).build();
                this.elasticsearchTemplate.index(indexQuery);
            } else {
            	// 有的话,更新
                Map<String, Object> map = new HashMap<>();
                map.put("lastUpdated", ul.getUpdated());
                map.put("updated", System.currentTimeMillis());
                map.put("address", address);
                map.put("location", new GeoPoint(latitude, longitude));

                UpdateRequest updateRequest = new UpdateRequest();
                updateRequest.doc(map);

                UpdateQuery updateQuery = new UpdateQueryBuilder()
                        .withId(userId.toString())
                        .withClass(UserLocationEs.class)
                        .withUpdateRequest(updateRequest).build();

                this.elasticsearchTemplate.update(updateQuery);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 查询用户的位置信息
     */
    public UserLocationEs queryByUserId(Long userId) {
        GetQuery getQuery = new GetQuery();
        getQuery.setId(userId.toString());
        return this.elasticsearchTemplate.queryForObject(getQuery, UserLocationEs.class);
    }

    /**
     * 根据位置搜索
     *
     * @param longitude 经度
     * @param latitude  纬度
     * @param distance  距离(米)
     * @param page      页数
     * @param pageSize  页面大小
     */
    public Page<UserLocationEs> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {
        String fieldName = "location";

        // 实现了SearchQuery接口,用于组装QueryBuilder和SortBuilder以及Pageable等
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 分页
        PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
        nativeSearchQueryBuilder.withPageable(pageRequest);

        // 定义bool查询
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        // 以某点为中心,搜索指定范围
        GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
        distanceQueryBuilder.point(latitude, longitude);

        // 定义查询单位:公里
        distanceQueryBuilder.distance(distance / 1000, DistanceUnit.KILOMETERS);

        boolQueryBuilder.must(distanceQueryBuilder);
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);

        // 按距离升序
        GeoDistanceSortBuilder distanceSortBuilder =
                new GeoDistanceSortBuilder(fieldName, latitude, longitude);
        distanceSortBuilder.unit(DistanceUnit.KILOMETERS);
        distanceSortBuilder.order(SortOrder.ASC); 
        nativeSearchQueryBuilder.withSort(distanceSortBuilder);

        return this.elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), UserLocationEs.class);
    }

}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestUserLocationService {

    @Autowired
    @SuppressWarnings("all")
    private UserLocationService userLocationService;

    @Test
    public void testUpdateUserLocation() {
        this.userLocationService.updateUserLocation(1L, 121.512253, 31.24094, "金茂大厦");
        this.userLocationService.updateUserLocation(2L, 121.506377, 31.245105, "东方明珠广播电视塔");
        this.userLocationService.updateUserLocation(10L, 121.508815, 31.243844, "陆家嘴地铁站");
        this.userLocationService.updateUserLocation(12L, 121.511999, 31.239185, "上海中心大厦");
        this.userLocationService.updateUserLocation(25L, 121.493444, 31.240513, "上海市公安局");
        this.userLocationService.updateUserLocation(27L, 121.494108, 31.247011, "上海外滩美术馆");
        this.userLocationService.updateUserLocation(30L, 121.462452, 31.253463, "上海火车站");
        this.userLocationService.updateUserLocation(32L, 121.81509, 31.157478, "上海浦东国际机场");
        this.userLocationService.updateUserLocation(34L, 121.327908, 31.20033, "虹桥火车站");
        this.userLocationService.updateUserLocation(38L, 121.490155, 31.277476, "鲁迅公园");
        this.userLocationService.updateUserLocation(40L, 121.425511, 31.227831, "中山公园");
        this.userLocationService.updateUserLocation(43L, 121.594194, 31.207786, "张江高科");
    }


    @Test
    public void testQuery() {
        Page<UserLocationEs> userLocationPage = this.userLocationService.queryUserFromLocation(121.512253, 31.24094, 1000d, 1, 100);
        userLocationPage.forEach(userLocationES -> {
            System.out.println(userLocationES);
            double distance = GeoDistance.ARC.calculate(31.24094, 121.512253, userLocationES.getLocation().getLat(), userLocationES.getLocation().getLon(), DistanceUnit.METERS);
            System.out.println("距离我 : " + distance + "米");
        });
    }

}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值