Redis List:打造高效消息队列的秘密武器【redis实战 一】
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
在数字化世界中,了解“你我之间的距离”不仅仅是一句浪漫的话语,也是现代应用不可或缺的功能之一。无论是社交应用中的“附近的人”,还是物流追踪中的“包裹距离”,对距离的精准计算和周围环境的了解都显得至关重要。本文将带你进入Spring Boot和Redis Geo的强大世界,探索如何轻松实现这些看似复杂的功能。
Redis Geo是Redis数据库提供的一个功能强大的地理空间位置处理模块。它允许你存储地理位置信息并执行各种地理空间查询。下面是一些关键点:
GEODIST
, GEORADIUS
和 GEOPOS
。Redis Geo与其他数据库系统(如关系型数据库、NoSQL数据库或专用的地理空间数据库)相比,在处理地理空间数据方面具有多项优势。这些优势主要源于Redis的性能、效率以及与地理空间数据处理紧密结合的特定命令。以下是Redis Geo相比其他数据库的主要优势:
GEOADD
, GEODIST
, GEORADIUS
等,无需复杂的SQL查询或专用的地理空间函数。虽然Redis Geo有许多优势,但它也有自己的局限性,例如它不支持像PostGIS那样复杂的地理空间查询和分析功能。此外,作为内存数据库,如果没有适当的持久化策略,可能会有数据丢失的风险。因此,选择合适的数据库还需要根据你的具体需求、资源和预期的使用场景来决定。在一些情况下,将Redis Geo与其他数据库系统结合使用,可能会得到更好的效果。
本实战我们主要是实现周围的人这个功能,也可以理解为滴滴打车中根据距离派车这样的逻辑
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
在实际应用中应该分为三层,业务代码还是要写入到service层中
package fun.bo.controller;
import lombok.Data;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author xiaobo
*/
@RestController
@RequestMapping("/api")
public class GeoController {
// 真实业务场景中不建议将业务代码放到controller层,且下面的实体放到dto或者vo中
@Resource
private RedisTemplate<String, String> redisTemplate;
private static final String GEO_KEY = "locations";
// 新增坐标点
@PostMapping("/addLocation")
public ResponseEntity<?> addLocation(@RequestBody GeoLocation location) {
// 增加坐标点
redisTemplate.opsForGeo().add(GEO_KEY, new Point(location.getLongitude(), location.getLatitude()), location.getName());
return ResponseEntity.ok("坐标添加成功");
}
// 查询附近的点
@PostMapping("/searchNearby")
public ResponseEntity<?> searchNearby(@RequestBody SearchRequest request) {
Circle circle = new Circle(new Point(request.getLongitude(), request.getLatitude()), new Distance(request.getRadius(), Metrics.KILOMETERS));
// 获取范围坐标点
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(GEO_KEY, circle, RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().limit(10));
return ResponseEntity.ok(results);
}
// 辅助类定义
@Data
public static class GeoLocation {
private String name;
private double latitude;
private double longitude;
// 省略getter和setter方法
}
@Data
public static class SearchRequest {
private double latitude;
private double longitude;
private double radius;
// 省略getter和setter方法
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>地图坐标交互</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<style>
#map { height: 400px; }
.input-group { margin: 10px 0; }
</style>
</head>
<body>
<div id="map"></div>
<div class="input-group">
<input type="text" id="locationName" placeholder="位置名称">
<input type="text" id="coords" placeholder="点击地图获取坐标">
<button onclick="addLocation()">新增</button>
</div>
<div class="input-group">
<input type="text" id="searchCoords" placeholder="点击地图设置查询中心">
<input type="number" id="radius" placeholder="半径(km)">
<button onclick="searchNearby()">查询附近的点</button>
</div>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script>
var map = L.map('map').setView([34.3416, 108.9398], 5);
var searchCircle; // 用于引用查询圈
L.tileLayer('http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}', {
attribution: 'Map data © <a href="https://www.amap.com/">高德地图</a>'
}).addTo(map);
map.on('click', function(e) {
document.getElementById('coords').value = e.latlng.lat + ", " + e.latlng.lng;
document.getElementById('searchCoords').value = e.latlng.lat + ", " + e.latlng.lng;
// 模拟反向地理编码获取位置名称
});
function addLocation() {
var name = document.getElementById('locationName').value || 'Unnamed Location';
var coords = document.getElementById('coords').value.split(", ");
fetch('/api/addLocation', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: name, latitude: coords[0], longitude: coords[1]}),
})
.then(response => response.text())
.then(data => alert(data))
.catch(error => console.error('Error:', error));
}
function searchNearby() {
var searchCoords = document.getElementById('searchCoords').value.split(", ");
var radius = document.getElementById('radius').value;
// 绘制查询圈
if (searchCircle) {
map.removeLayer(searchCircle);
}
searchCircle = L.circle([searchCoords[0], searchCoords[1]], {
color: 'blue',
fillColor: '#f03',
fillOpacity: 0.1,
radius: radius * 1000 // Leaflet的半径单位是米
}).addTo(map);
fetch('/api/searchNearby', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({latitude: searchCoords[0], longitude: searchCoords[1], radius: radius}),
})
.then(response => response.json())
.then(data => {
// console.log(data)
data.content.forEach(result => {
var point = result.content.point;
var name = result.content.name;
var marker = L.marker([point.y, point.x]).addTo(map);
marker.bindPopup(name).openPopup();
});
})
.catch(error => console.error('Error:', error));
}
</script>
</body>
</html>