分享一個 SpringBoot + Redis 實現「查詢附近的人」的小技巧

2023-09-11 12:03:03

前言

SpringDataRedis提供了十分簡單的地理位置定位的功能,今天我就用一小段程式碼告訴大家如何實現。

正文

1、引入依賴

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

2、更新使用者位置資訊

編寫一個更新使用者位置資訊的介面,其中幾個引數的含義如下:

  • userId:要更新位置資訊或查詢附近的人的使用者ID。
  • longitude:新的經度值或查詢附近的人時指定的中心經度。
  • latitude:新的緯度值或查詢附近的人時指定的中心緯度。

@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 更新使用者位置資訊
@PostMapping("/{userId}/location")
public void updateUserLocation(@PathVariable long userId, 
                               @RequestParam double longitude, 
                               @RequestParam double latitude) {

    String userLocationKey = "user_location";
    // 使用Redis的地理位置操作物件,將使用者的經緯度資訊新增到指定的key中
    redisTemplate.opsForGeo().add(userLocationKey, 
                        new Point(longitude, latitude), userId);
}

3、獲取附近的人

編寫一個獲取附近的人的介面


@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 獲取附近的人
@GetMapping("/{userId}/nearby")
public List<Object> getNearbyUsers(@PathVariable long userId, 
                                   @RequestParam double longitude, 
                                   @RequestParam double latitude,
                                   @RequestParam double radius) {

    String userLocationKey = "user_location";
    Distance distance = new Distance(radius, Metrics.KILOMETERS);
    Circle circle = new Circle(new Point(longitude, latitude), distance);

    // 使用Redis的地理位置操作物件,在指定範圍內查詢附近的使用者位置資訊
    GeoResults<GeoLocation<Object>> geoResults = redisTemplate
                                    .opsForGeo()
                                    .radius(userLocationKey, circle);

    List<Object> nearbyUsers = new ArrayList<>();
    for (GeoResult<GeoLocation<Object>> geoResult : geoResults.getContent()) {
        Object memberId = geoResult.getContent().getName();
        // 排除查詢使用者本身
        if (!memberId.equals(userId)) {
            nearbyUsers.add(memberId);
        }
    }
    return nearbyUsers;
}

其中幾個重要屬性的含義如下:

  • Distance:Spring Data Redis中的一個類,用於表示距離。它可以用於指定搜尋半徑或者計算兩點之間的距離。在範例程式碼中,我們建立了一個Distance物件來指定搜尋範圍的半徑,並使用Metrics.KILOMETERS表示以千米為單位的距離。

  • Circle:Spring Data Redis中的一個類,用於表示圓形區域。它由一箇中心點(用Point表示)和一個半徑(用Distance表示)組成。在範例程式碼中,我們通過傳入中心點和距離建立了一個Circle物件,用於定義附近人搜尋的圓形區域。

  • GeoResults:Spring Data Redis中的一個類,用於表示地理位置查詢的結果。它包含了一個Content屬性,該屬性是一個List<GeoResult<GeoLocation<Object>>>型別的列表,其中每個GeoResult物件都包含了地理位置資訊以及與該位置相關的其他資料。在範例程式碼中,我們通過呼叫redisTemplate.opsForGeo().radius()方法返回了一個GeoResults物件,其中包含了在指定範圍內的所有地理位置結果。

4、完整程式碼如下

為了用更少的程式碼片段讓大家一目瞭然,所以都寫在controller中,應用在專案裡面時最好把其中的實現部分都轉移到service中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 更新使用者位置資訊
    @PostMapping("/{userId}/location")
    public void updateUserLocation(@PathVariable long userId, 
                                   @RequestParam double longitude, 
                                   @RequestParam double latitude) {
                                   
        String userLocationKey = "user_location";
        // 使用Redis的地理位置操作物件,將使用者的經緯度資訊新增到指定的key中
        redisTemplate.opsForGeo().add(userLocationKey, 
                                new Point(longitude, latitude), userId);
    }

    // 獲取附近的人
    @GetMapping("/{userId}/nearby")
    public List<Object> getNearbyUsers(@PathVariable long userId, 
                                       @RequestParam double longitude, 
                                       @RequestParam double latitude, 
                                       @RequestParam double radius) {
                                       
        String userLocationKey = "user_location";
        Distance distance = new Distance(radius, Metrics.KILOMETERS);
        Circle circle = new Circle(new Point(longitude, latitude), distance);
        
        // 使用Redis的地理位置操作物件,在指定範圍內查詢附近的使用者位置資訊
        GeoResults<GeoLocation<Object>> geoResults = redisTemplate
                                            .opsForGeo()
                                            .radius(userLocationKey, circle);
                                            
        List<Object> nearbyUsers = new ArrayList<>();
        for (GeoResult<GeoLocation<Object>> geoResult : geoResults.getContent()) {
            Object memberId = geoResult.getContent().getName();
            // 排除查詢使用者本身
            if (!memberId.equals(userId)) {
                nearbyUsers.add(memberId);
            }
        }
        return nearbyUsers;
    }
}

總結

SpringDataRedis本身也是對Redis底層命令的封裝,所以Jedis裡面其實也提供了差不多的實現,大家可以自己去試一試。

如果有redis相關的面試,如果能說出Redis的Geo命令,面試官就知道你有研究過,是可以大大加分的。

側面證明了你對Redis的瞭解不侷限於簡單的set、get命令,希望這篇文章對大家有所幫助。


如果喜歡,記得點贊關注↓↓↓,持續分享乾貨!