本文目录导读:

我来提供一个完整的Java实现"附近的人"功能的案例,包括多种实现方案。
基于MySQL的简单实现
数据模型
@Entity
@Table(name = "user_location")
public class UserLocation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false, unique = true)
private Long userId;
@Column(name = "user_name")
private String userName;
@Column(name = "longitude", nullable = false)
private Double longitude;
@Column(name = "latitude", nullable = false)
private Double latitude;
@Column(name = "update_time")
private LocalDateTime updateTime;
// getter/setter...
}
附近的人查询实现
@Service
public class NearbyUserService {
@Autowired
private UserLocationRepository locationRepository;
// 地球半径(公里)
private static final double EARTH_RADIUS_KM = 6371.0;
/**
* 查找附近的人(使用Haversine公式)
*/
public List<UserLocation> findNearbyUsers(double longitude, double latitude, double distanceKm, int limit) {
// 计算经纬度范围(粗略过滤)
double deltaLat = distanceKm / 110.574;
double deltaLng = distanceKm / (111.320 * Math.cos(Math.toRadians(latitude)));
double minLat = latitude - deltaLat;
double maxLat = latitude + deltaLat;
double minLng = longitude - deltaLng;
double maxLng = longitude + deltaLng;
// 先按范围查询,再精确计算距离
List<UserLocation> candidates = locationRepository.findByLatitudeBetweenAndLongitudeBetween(
minLat, maxLat, minLng, maxLng);
// 精确计算距离并按距离排序
return candidates.stream()
.map(user -> {
user.setDistance(calculateDistance(latitude, longitude,
user.getLatitude(), user.getLongitude()));
return user;
})
.filter(user -> user.getDistance() <= distanceKm)
.sorted(Comparator.comparing(UserLocation::getDistance))
.limit(limit)
.collect(Collectors.toList());
}
/**
* 计算两个坐标点之间的距离(Haversine公式)
*/
public static double calculateDistance(double lat1, double lng1, double lat2, double lng2) {
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
* Math.sin(dLng / 2) * Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS_KM * c;
}
}
Repository接口
@Repository
public interface UserLocationRepository extends JpaRepository<UserLocation, Long> {
List<UserLocation> findByLatitudeBetweenAndLongitudeBetween(
double minLat, double maxLat, double minLng, double maxLng);
}
基于Redis Geo的优化实现
Redis配置
@Configuration
public class 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;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}
Redis Geo服务
@Service
public class RedisGeoService {
private static final String GEO_KEY = "user:geo";
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 添加用户位置
*/
public void addUserLocation(Long userId, double longitude, double latitude) {
redisTemplate.opsForGeo().add(GEO_KEY,
new Point(longitude, latitude),
String.valueOf(userId));
}
/**
* 查找附近的人
*/
public List<NearbyUserDTO> findNearbyUsers(double longitude, double latitude,
double distanceKm, int limit, boolean sortByDistance) {
// 使用Redis GEO搜索
Circle circle = new Circle(new Point(longitude, latitude),
new Distance(distanceKm, RedisGeoCommands.DistanceUnit.KILOMETERS));
RedisGeoCommands.GeoRadiusCommandArgs args =
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates()
.limit(limit);
if (sortByDistance) {
args.sortAscending();
} else {
args.sortDescending();
}
GeoResults<RedisGeoCommands.GeoLocation<String>> results =
redisTemplate.opsForGeo().radius(GEO_KEY, circle, args);
// 转换结果
List<NearbyUserDTO> nearbyUsers = new ArrayList<>();
if (results != null) {
for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : results) {
RedisGeoCommands.GeoLocation<String> location = result.getContent();
RedisGeoCommands.Distance distance = result.getDistance();
NearbyUserDTO dto = new NearbyUserDTO();
dto.setUserId(Long.parseLong(location.getName()));
dto.setDistance(distance.getValue());
dto.setLongitude(location.getPoint().getX());
dto.setLatitude(location.getPoint().getY());
nearbyUsers.add(dto);
}
}
return nearbyUsers;
}
/**
* 计算两个用户之间的距离
*/
public double getDistance(Long userId1, Long userId2) {
Distance distance = redisTemplate.opsForGeo().distance(GEO_KEY,
String.valueOf(userId1), String.valueOf(userId2),
RedisGeoCommands.DistanceUnit.KILOMETERS);
return distance != null ? distance.getValue() : -1;
}
/**
* 删除用户位置
*/
public void removeUser(Long userId) {
redisTemplate.opsForGeo().remove(GEO_KEY, String.valueOf(userId));
}
/**
* 获取用户位置
*/
public Point getUserLocation(Long userId) {
List<Point> points = redisTemplate.opsForGeo().position(GEO_KEY,
String.valueOf(userId));
return points != null && !points.isEmpty() ? points.get(0) : null;
}
}
DTO对象
public class NearbyUserDTO {
private Long userId;
private String userName;
private Double distance;
private Double longitude;
private Double latitude;
private String avatar;
// getter/setter...
}
基于MongoDB的实现
实体类
@Document(collection = "user_locations")
public class UserGeoLocation {
@Id
private Long userId;
private String userName;
// MongoDB地理位置字段
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private Point location;
@Field("update_time")
private LocalDateTime updateTime;
// getter/setter...
}
Repository
public interface UserGeoRepository extends MongoRepository<UserGeoLocation, Long> {
/**
* 查找附近的用户
*/
@Query("{location: {$nearSphere: {$geometry: {type: 'Point', coordinates: [?0, ?1]}, $maxDistance: ?2}}}")
List<UserGeoLocation> findNearbyUsers(double longitude, double latitude, double maxDistance);
/**
* 按距离排序查找
*/
List<UserGeoLocation> findByLocationNear(Point point, Distance maxDistance);
}
完整业务实现
完整服务实现
@Service
public class NearbyUserServiceImpl implements NearbyUserService {
@Autowired
private RedisGeoService redisGeoService;
@Autowired
private UserService userService;
@Autowired
private UserLocationRepository locationRepository;
/**
* 更新用户位置
*/
@Transactional
public void updateLocation(Long userId, double longitude, double latitude) {
// 1. 保存到MySQL(用于历史记录)
UserLocation userLocation = locationRepository.findByUserId(userId);
if (userLocation != null) {
userLocation.setLongitude(longitude);
userLocation.setLatitude(latitude);
userLocation.setUpdateTime(LocalDateTime.now());
} else {
userLocation = new UserLocation();
userLocation.setUserId(userId);
userLocation.setLongitude(longitude);
userLocation.setLatitude(latitude);
userLocation.setUpdateTime(LocalDateTime.now());
}
locationRepository.save(userLocation);
// 2. 更新到Redis(用于快速查询)
redisGeoService.addUserLocation(userId, longitude, latitude);
}
/**
* 获取附近的人列表
*/
public List<NearbyUserDTO> getNearbyUsers(double longitude, double latitude,
double distance, int limit, String otherParams) {
// 1. 从Redis获取附近的用户ID和距离
List<NearbyUserDTO> nearbyUsers = redisGeoService.findNearbyUsers(
longitude, latitude, distance, limit, true);
// 2. 补充用户详细信息
for (NearbyUserDTO dto : nearbyUsers) {
User user = userService.getUserById(dto.getUserId());
if (user != null) {
dto.setUserName(user.getUserName());
dto.setAvatar(user.getAvatar());
dto.setGender(user.getGender());
dto.setAge(calculateAge(user.getBirthday()));
}
}
return nearbyUsers;
}
/**
* 批量更新位置
*/
@Async
public void batchUpdateLocation(List<UserLocation> locations) {
locations.stream().forEach(location -> {
redisGeoService.addUserLocation(location.getUserId(),
location.getLongitude(), location.getLatitude());
});
}
}
缓存策略
@Service
public class LocationCacheService {
private static final String NEARBY_USERS_CACHE_KEY = "nearby:users:";
private static final Duration CACHE_TTL = Duration.ofMinutes(5);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 缓存附近的人结果
*/
public void cacheNearbyUsers(String key, List<NearbyUserDTO> users) {
String cacheKey = NEARBY_USERS_CACHE_KEY + key;
redisTemplate.opsForValue().set(cacheKey, users, CACHE_TTL);
}
/**
* 获取缓存的附近的人
*/
public List<NearbyUserDTO> getCachedNearbyUsers(String key) {
String cacheKey = NEARBY_USERS_CACHE_KEY + key;
Object cached = redisTemplate.opsForValue().get(cacheKey);
return cached != null ? (List<NearbyUserDTO>) cached : null;
}
/**
* 生成缓存key(使用网格算法)
*/
public String generateCacheKey(double latitude, double longitude, int zoom) {
// 使用GeoHash或网格ID作为缓存key
String geoHash = GeoHashUtil.encode(latitude, longitude);
return geoHash.substring(0, Math.min(geoHash.length(), zoom));
}
}
控制器层
@RestController
@RequestMapping("/api/nearby")
public class NearbyUserController {
@Autowired
private NearbyUserService nearbyUserService;
/**
* 更新位置
*/
@PostMapping("/location")
public ApiResponse<Void> updateLocation(@RequestBody LocationRequest request) {
nearbyUserService.updateLocation(request.getUserId(),
request.getLongitude(), request.getLatitude());
return ApiResponse.success();
}
/**
* 查询附近的人
*/
@GetMapping("/users")
public ApiResponse<List<NearbyUserDTO>> getNearbyUsers(
@RequestParam double longitude,
@RequestParam double latitude,
@RequestParam(defaultValue = "5") double distance,
@RequestParam(defaultValue = "20") int limit) {
List<NearbyUserDTO> users = nearbyUserService.getNearbyUsers(
longitude, latitude, distance, limit, null);
return ApiResponse.success(users);
}
/**
* 批量更新位置
*/
@PostMapping("/batch/location")
public ApiResponse<Void> batchUpdateLocation(@RequestBody List<LocationRequest> requests) {
nearbyUserService.batchUpdateLocation(requests);
return ApiResponse.success();
}
}
请求/响应实体
public class LocationRequest {
private Long userId;
private Double longitude;
private Double latitude;
// getter/setter...
}
public class ApiResponse<T> {
private Integer code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
// getter/setter...
}
高性能优化
网格算法优化
public class GridLocationService {
// 定义网格大小(约1公里)
private static final double GRID_SIZE_KM = 1.0;
/**
* 计算所在网格ID
*/
public String getGridId(double latitude, double longitude) {
int latGrid = (int) Math.round(latitude / GRID_SIZE_KM);
int lngGrid = (int) Math.round(longitude / GRID_SIZE_KM);
return latGrid + ":" + lngGrid;
}
/**
* 获取相邻网格(包含自身)
*/
public List<String> getAdjacentGrids(String gridId, int radius) {
String[] parts = gridId.split(":");
int latGrid = Integer.parseInt(parts[0]);
int lngGrid = Integer.parseInt(parts[1]);
List<String> grids = new ArrayList<>();
for (int i = -radius; i <= radius; i++) {
for (int j = -radius; j <= radius; j++) {
grids.add((latGrid + i) + ":" + (lngGrid + j));
}
}
return grids;
}
}
性能测试和优化
public class NearbyUserBenchmark {
public static void main(String[] args) {
// 生成测试数据
generateTestData(10000);
// 测试查询性能
long startTime = System.currentTimeMillis();
List<NearbyUserDTO> results = queryNearbyUsers(39.9, 116.4, 5, 20);
long endTime = System.currentTimeMillis();
System.out.println("查询耗时: " + (endTime - startTime) + "ms");
System.out.println("返回结果数: " + results.size());
}
private static void generateTestData(int count) {
Random random = new Random();
for (int i = 0; i < count; i++) {
double lat = 39.9 + random.nextDouble() * 2 - 1;
double lng = 116.4 + random.nextDouble() * 2 - 1;
System.out.println(String.format("INSERT INTO user_location (user_id, longitude, latitude) VALUES (%d, %.6f, %.6f);",
i + 1, lng, lat));
}
}
}
使用建议
方案选型
- 数据量较小(<10万):使用MySQL + 索引优化
- 数据量中等(10万-100万):使用Redis Geo
- 数据量较大(>100万):使用MongoDB Geolocation
优化策略
- 使用缓存减少数据库查询
- 采用网格算法缩小搜索范围
- 使用地理位置索引
- 定期清理过期位置数据
- 使用分区表存储历史位置数据
注意事项
- 经纬度精度控制
- 处理数据一致性
- 考虑用户隐私保护
- 设置合理的搜索范围限制
- 做好异常处理和数据校验
这个完整的案例提供了多种实现方案,可以根据实际业务需求选择合适的技术方案。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播