基于MongoDB/SpringBoot实现登录用户与其他用户的距离排序聚合查询
解决方案与优化指南
一、按距离排序的MongoDB聚合查询可行性分析与实现
可行性确认
$geoNear是MongoDB官方原生支持的地理空间聚合阶段,完全匹配你的需求:
- 依赖2dsphere索引是硬性要求,只要提前为经纬度字段创建该索引,就能启用地理距离计算能力
- 可通过配置
distanceField参数生成虚拟距离字段,聚合结果会自动包含该字段 - 支持前置过滤:结合
query参数可先筛选目标用户,再执行距离计算与排序
具体实现步骤
- 创建2dsphere索引
假设用户实体的经纬度字段为location(类型为org.springframework.data.geo.Point),通过Java代码创建索引:
// 使用MongoTemplate创建索引 mongoTemplate.indexOps(User.class) .ensureIndex(new GeospatialIndex("location").typed(GeospatialIndexType.GEO_2DSPHERE));
- 聚合查询代码实现
以下是按登录用户坐标排序其他用户的完整聚合示例:
// 登录用户的经纬度坐标 Point currentUserLocation = new Point(116.397, 39.908); String currentUserId = "user_123"; // 构建聚合流水线 Aggregation aggregation = Aggregation.newAggregation( // 地理距离计算阶段,生成虚拟distance字段 Aggregation.geoNear( NearQuery.near(currentUserLocation) .maxDistance(10000) // 可选:设置最大筛选距离(单位:米) .spherical(true), "distance" ), // 可选:排除当前用户 Aggregation.match(Criteria.where("userId").ne(currentUserId)), // 可选:覆盖默认排序($geoNear默认按距离升序) Aggregation.sort(Sort.by(Sort.Direction.ASC, "distance")), // 分页阶段(后续结合可选分页逻辑) Aggregation.skip(pageable.getOffset()), Aggregation.limit(pageable.getPageSize()) ); // 执行聚合,映射到包含distance字段的DTO AggregationResults<UserDistanceDTO> results = mongoTemplate.aggregate( aggregation, User.class, UserDistanceDTO.class ); List<UserDistanceDTO> sortedUsers = results.getMappedResults();
注:UserDistanceDTO需包含原User实体的所有字段,并新增Double distance属性用于接收虚拟距离值。
二、代码优化方案
1. 整合重复API(getUsers、getFavs等)
抽象通用查询服务,封装过滤、排序、分页的核心逻辑,避免重复造轮子:
// 通用查询接口 public interface GenericQueryService<T> { List<T> query(Criteria criteria, Sort sort, Pageable pageable); } // 通用实现类 @Service public class GenericQueryServiceImpl<T> implements GenericQueryService<T> { private final MongoTemplate mongoTemplate; private final Class<T> entityClass; public GenericQueryServiceImpl(MongoTemplate mongoTemplate, Class<T> entityClass) { this.mongoTemplate = mongoTemplate; this.entityClass = entityClass; } @Override public List<T> query(Criteria criteria, Sort sort, Pageable pageable) { Query query = new Query(criteria); if (sort != null) query.with(sort); if (pageable != null && pageable.isPaged()) query.with(pageable); return mongoTemplate.find(query, entityClass); } } // 业务服务复用示例 @Service public class UserService { private final GenericQueryService<User> userQueryService; public UserService(MongoTemplate mongoTemplate) { this.userQueryService = new GenericQueryServiceImpl<>(mongoTemplate, User.class); } public List<User> getUsers(Criteria criteria, Sort sort, Pageable pageable) { return userQueryService.query(criteria, sort, pageable); } } @Service public class FavService { private final GenericQueryService<Fav> favQueryService; public FavService(MongoTemplate mongoTemplate) { this.favQueryService = new GenericQueryServiceImpl<>(mongoTemplate, Fav.class); } public List<Fav> getFavs(Criteria criteria, Sort sort, Pageable pageable) { return favQueryService.query(criteria, sort, pageable); } }
2. 实现可选分页
通过判断分页参数的状态,支持全量/分页数据切换:
// 修改通用查询服务的分页逻辑 @Override public List<T> query(Criteria criteria, Sort sort, Pageable pageable) { Query query = new Query(criteria); if (sort != null) query.with(sort); // 当pageable为null,或pageSize=0时返回全量数据 if (pageable != null && pageable.getPageSize() > 0) { query.with(pageable); } return mongoTemplate.find(query, entityClass); } // 调用示例 // 获取全量数据 List<User> allUsers = userService.getUsers(criteria, sort, null); // 或显式指定全量 List<User> allUsers = userService.getUsers(criteria, sort, PageRequest.of(0, 0)); // 获取分页数据 List<User> pageUsers = userService.getUsers(criteria, sort, PageRequest.of(0, 10));
3. 支持多条件AND/OR组合过滤
利用MongoDB的Criteria类实现动态条件组合:
// AND组合条件 Criteria andCriteria = new Criteria() .andOperator( Criteria.where("lastLoginAt").gte(LocalDateTime.now().minusDays(7)), Criteria.where("status").is("ACTIVE") ); // OR组合条件 Criteria orCriteria = new Criteria() .orOperator( Criteria.where("createdAt").gte(LocalDateTime.now().minusMonths(1)), Criteria.where("role").is("ADMIN") ); // AND嵌套OR的复杂条件 Criteria complexCriteria = new Criteria() .andOperator( Criteria.where("status").is("ACTIVE"), new Criteria().orOperator( Criteria.where("lastLoginAt").gte(LocalDateTime.now().minusDays(7)), Criteria.where("role").is("ADMIN") ) ); // 执行查询 List<User> users = userService.getUsers(complexCriteria, Sort.by("createdAt"), pageable);
内容的提问来源于stack exchange,提问作者The Old County
相关产品推荐
相关产品推荐

