SpringBoot與GeoHash整合,實現騎手的實時就近派單功能
作者:Java知識日歷
通過使用GeoHash結合Redis的地理空間功能,能夠實時管理和查詢騎手的位置信息,并根據訂單量和評分等因素動態分配最近的騎手來完成配送任務。
通過使用GeoHash結合Redis的地理空間功能,能夠實時管理和查詢騎手的位置信息,并根據訂單量和評分等因素動態分配最近的騎手來完成配送任務。
我們為什么使用GeoHash?
- 空間索引: GeoHash是一種將地理坐標(經緯度)編碼為字符串的算法,可以用于空間索引。 這使得我們可以方便地在Redis這樣的內存數據庫中存儲和檢索地理位置數據。
- 快速篩選附近騎手: 通過GeoHash前綴匹配策略,可以在較短的時間內篩選出一定范圍內的騎手。 Redis提供了豐富的地理空間命令(如 GEOADD, GEORADIUS),可以直接利用這些命令來實現高效的地理空間查詢。
- 減少計算開銷: 相比于傳統的距離計算方法(如Haversine公式),GeoHash可以通過字符串比較來快速確定位置關系。 這減少了復雜的數學運算,提高了查詢性能。
- 支持多級精度: GeoHash支持多種精度級別,可以根據需求調整搜索范圍。 例如,較長的GeoHash字符串表示更精確的位置,而較短的字符串則覆蓋更大的區域。這為我們提供了靈活性,可以根據不同的應用場景調整查詢精度。
實現這個功能的關鍵點有哪些?
- 高效的位置存儲和查詢
- 動態權重調整: 根據騎手當前訂單量、歷史好評率計算派單優先級
- 實時更新騎手位置,實時位置數據對于準確派單至關重要。
代碼實操
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>delivery-system</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>delivery-system</name>
<description>Demo project for Spring Boot and GeoHash integration</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>application.properties
server.port=8080
spring.redis.host=localhost
spring.redis.port=6379Redis Config
package com.example.deliverysystem.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
publicclass RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}騎手
package com.example.deliverysystem.model;
import lombok.Data;
@Data
publicclass Rider {
private String id; // 騎手ID
privatedouble latitude; // 緯度
privatedouble longitude; // 經度
privateint orderCount; // 當前訂單數量
privatedouble rating; // 歷史平均評分(0-5)
}Repository
package com.example.deliverysystem.repository;
import com.example.deliverysystem.model.Rider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
publicclass RiderRepository {
privatefinal GeoOperations<String, String> geoOps;
@Autowired
public RiderRepository(RedisTemplate<String, String> redisTemplate) {
this.geoOps = redisTemplate.opsForGeo();
}
/**
* 添加騎手到Redis中的地理位置集合
*
* @param key Redis鍵名
* @param rider 騎手對象
*/
public void addRider(String key, Rider rider) {
Point point = new Point(rider.getLongitude(), rider.getLatitude());
geoOps.add(key, point, rider.getId());
}
/**
* 查找指定半徑范圍內的騎手
*
* @param key Redis鍵名
* @param referencePoint 參考點(經緯度)
* @param distance 半徑距離
* @return 附近的騎手列表
*/
public List<RedisGeoCommands.GeoResult<RedisGeoCommands.GeoLocation<String>>> findNearbyRiders(String key, Point referencePoint, Distance distance) {
Circle circle = new Circle(referencePoint, distance);
return geoOps.radius(key, circle);
}
/**
* 獲取騎手詳情
*
* @param results 附近騎手的結果列表
* @return 騎手詳情映射表
*/
public Map<Object, Object> getRiderDetails(List<RedisGeoCommands.GeoResult<RedisGeoCommands.GeoLocation<String>>> results) {
// 這里可以進一步獲取每個騎手的詳細信息,例如從數據庫中讀取。
// 為了簡化示例,我們僅返回騎手ID列表。
return results.stream()
.collect(java.util.stream.Collectors.toMap(
result -> result.getContent().getName(),
result -> null));
}
}Service
package com.example.deliverysystem.service;
import com.example.deliverysystem.model.Rider;
import com.example.deliverysystem.repository.RiderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@Service
publicclass DeliveryService {
privatefinal RiderRepository riderRepository;
@Autowired
public DeliveryService(RiderRepository riderRepository) {
this.riderRepository = riderRepository;
}
/**
* 添加騎手到系統
*
* @param key Redis鍵名
* @param rider 騎手對象
*/
public void addRider(String key, Rider rider) {
riderRepository.addRider(key, rider);
}
/**
* 查找最近的騎手
*
* @param key Redis鍵名
* @param latitude 目標緯度
* @param longitude 目標經度
* @param radiusInKilometers 查詢半徑(公里)
* @return 最近的騎手列表,按優先級排序
*/
public List<Rider> findNearestRiders(String key, double latitude, double longitude, double radiusInKilometers) {
Point referencePoint = new Point(longitude, latitude);
Distance radius = new Distance(radiusInKilometers, Metrics.KILOMETERS);
List<RedisGeoCommands.GeoResult<RedisGeoCommands.GeoLocation<String>>> nearbyRiders =
riderRepository.findNearbyRiders(key, referencePoint, radius);
Map<Object, Object> riderDetails = riderRepository.getRiderDetails(nearbyRiders);
// 假設這里可以根據騎手ID獲取其他屬性。
// 實際場景中,這些信息可能來自數據庫或其他數據源。
return nearbyRiders.stream()
.map(result -> {
Rider rider = new Rider();
rider.setId(result.getContent().getName());
// 根據實際需求獲取其他屬性。
return rider;
})
.sorted(Comparator.comparingDouble(this::calculatePriority))
.toList();
}
/**
* 計算騎手的派單優先級
*
* @param rider 騎手對象
* @return 優先級值(數值越大優先級越高)
*/
private double calculatePriority(Rider rider) {
// 簡單的優先級計算方法:較低的訂單量和較高的評分意味著更高的優先級。
// 具體公式可以根據實際業務需求調整。
return -(rider.getOrderCount() * 1.0 / 5 + rider.getRating());
}
}Controller
package com.example.deliverysystem.controller;
import com.example.deliverysystem.model.Rider;
import com.example.deliverysystem.service.DeliveryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/delivery")
publicclass DeliveryController {
privatefinal DeliveryService deliveryService;
@Autowired
public DeliveryController(DeliveryService deliveryService) {
this.deliveryService = deliveryService;
}
/**
* 添加新的騎手
*
* @param rider 新的騎手對象
*/
@PostMapping("/riders")
public void addRider(@RequestBody Rider rider) {
deliveryService.addRider("riders", rider);
}
/**
* 查找最近的騎手
*
* @param latitude 目標緯度
* @param longitude 目標經度
* @param radiusInKilometers 查詢半徑(公里,默認5公里)
* @return 最近的騎手列表
*/
@GetMapping("/nearest-rider/{latitude}/{longitude}")
public List<Rider> findNearestRiders(@PathVariable double latitude,
@PathVariable double longitude,
@RequestParam(defaultValue = "5") double radiusInKilometers) {
return deliveryService.findNearestRiders("riders", latitude, longitude, radiusInKilometers);
}
}Application
package com.example.deliverysystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeliverySystemApplication {
public static void main(String[] args) {
SpringApplication.run(DeliverySystemApplication.class, args);
}
}測試
添加第一個騎手
curl -X POST http://localhost:8080/api/delivery/riders \
-H "Content-Type: application/json" \
-d '{"id": "rider1", "latitude": 39.9042, "longitude": 116.4074, "orderCount": 2, "rating": 4.5}'Respons
空響應(HTTP狀態碼200)
添加第二個騎手
curl -X POST http://localhost:8080/api/delivery/riders \
-H "Content-Type: application/json" \
-d '{"id": "rider2", "latitude": 39.9087, "longitude": 116.3975, "orderCount": 1, "rating": 4.8}'Respons
空響應(HTTP狀態碼200)
查詢最近的騎手
- 查詢位置為 (39.9063, 116.4039) 附近5公里范圍內的騎手
curl -X GET "http://localhost:8080/api/delivery/nearest-rider/39.9063/116.4039"Respons
[
{
"id": "rider1",
"latitude": 39.9042,
"longitude": 116.4074,
"orderCount": 2,
"rating": 4.5
},
{
"id": "rider2",
"latitude": 39.9087,
"longitude": 116.3975,
"orderCount": 1,
"rating": 4.8
}
]責任編輯:武曉燕
來源:
Java知識日歷





































