七(7)探花功能-MongoDB地理位置查询-附近的人
课程总结
1.探花功能
业务需求
执行过程
2.MongoDB的地理位置查询
地理位置查询的应用场景
查询案例
3.搜附近
上报地理位置
使用MongoDB搜索附近
一. 探花左划右滑
探花功能是将推荐的好友随机的通过卡片的形式展现出来,用户可以选择左滑、右滑操作,左滑:“不喜欢”,右滑:“喜欢”。
喜欢:如果双方喜欢,那么就会成为好友。
如果已经喜欢或不喜欢的用户在列表中不再显示。
1-1 技术支持
1. 数据库表
2. Redis缓存
探花功能使用Redis缓存提高查询效率。
对于喜欢/不喜欢功能,使用Redis中的set进行存储。Redis 的 Set 是无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。使用Set可以方便的实现交集,并集等个性化查询。
数据规则:
喜欢:USER_LIKE_SET_{ID}
不喜欢:USER_NOT_LIKE_SET_{ID}
1-2 查询推荐用户
1. 接口文档
单次查询随机返回10条推荐用户数据
需要排除已喜欢/不喜欢的数据
2. 思路分析
查询探花卡片推荐用户列表
1、查询已喜欢/不喜欢的用户列表
2、查询推荐列表,并排除已喜欢/不喜欢用户
3、如果推荐列表不存在,构造默认数据
3. 实体对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "user_like")
public class UserLike implements java.io.Serializable {private static final long serialVersionUID = 6739966698394686523L;private ObjectId id;@Indexedprivate Long userId; //用户id,自己@Indexedprivate Long likeUserId; //喜欢的用户id,对方private Boolean isLike; // 是否喜欢private Long created; //创建时间private Long updated; // 更新时间}
4. controller
TanhuaController
/*** 探花-推荐用户列表*/@GetMapping("/cards")public ResponseEntity queryCardsList() {List<TodayBest> list = this.tanhuaService.queryCardsList();return ResponseEntity.ok(list);}
5. service
TanhuaService
#默认推荐列表
tanhua:default:recommend:users: 2,3,8,10,18,20,24,29,27,32,36,37,56,64,75,88
@Value("${tanhua.default.recommend.users}")private String recommendUser;//探花-推荐用户列表public List<TodayBest> queryCardsList() {//1、调用推荐API查询数据列表(排除喜欢/不喜欢的用户,数量限制)List<RecommendUser> users = recommendUserApi.queryCardsList(UserHolder.getUserId(),10);//2、判断数据是否存在,如果不存在,构造默认数据 1,2,3if(CollUtil.isEmpty(users)) {users = new ArrayList<>();String[] userIdS = recommendUser.split(",");for (String userId : userIdS) {RecommendUser recommendUser = new RecommendUser();recommendUser.setUserId(Convert.toLong(userId));recommendUser.setToUserId(UserHolder.getUserId());recommendUser.setScore(RandomUtil.randomDouble(60, 90));users.add(recommendUser);}}//3、构造VOList<Long> ids = CollUtil.getFieldValues(users, "userId", Long.class);Map<Long, UserInfo> infoMap = userInfoApi.findByIds(ids, null);List<TodayBest> vos = new ArrayList<>();for (RecommendUser user : users) {UserInfo userInfo = infoMap.get(user.getUserId());if(userInfo != null) {TodayBest vo = TodayBest.init(userInfo, user);vos.add(vo);}}return vos;}
6. API实现
RecommendUserApi
/*** 查询探花列表,查询时需要排除喜欢和不喜欢的用户*/List<RecommendUser> queryCardsList(Long userId, int count);
RecommendUserApiImpl
/*** 查询探花列表,查询时需要排除喜欢和不喜欢的用户* 1、排除喜欢,不喜欢的用户* 2、随机展示* 3、指定数量*/public List<RecommendUser> queryCardsList(Long userId, int counts) {//1、查询喜欢不喜欢的用户IDList<UserLike> likeList = mongoTemplate.find(Query.query(Criteria.where("userId").is(userId)), UserLike.class);List<Long> likeUserIdS = CollUtil.getFieldValues(likeList, "likeUserId", Long.class);//2、构造查询推荐用户的条件Criteria criteria = Criteria.where("toUserId").is(userId).and("userId").nin(likeUserIdS);//3、使用统计函数,随机获取推荐的用户列表TypedAggregation<RecommendUser> newAggregation = TypedAggregation.newAggregation(RecommendUser.class,Aggregation.match(criteria),//指定查询条件Aggregation.sample(counts));AggregationResults<RecommendUser> results = mongoTemplate.aggregate(newAggregation, RecommendUser.class);//4、构造返回return results.getMappedResults();}
1-3 喜欢&不喜欢(Redis-set)
用户的喜欢与不喜欢列表需要保存在redis中,为了防止redis中的数据丢失,同时需要将数据保存到mongodb进行持久化保存。
左滑:“不喜欢”,右滑:“喜欢”,如果双方喜欢,那么就会成为好友。
1. 接口文档
喜欢接口
不喜欢接口
2. 思路分析
喜欢思路分析
喜欢步骤
1、编写API层方法,将喜欢数据存入MongoDB
查询是否存在喜欢数据
如果存在更新数据,如果不存在保存数据
2、Service层进行代码调用
调用API层保存喜欢数据,喜欢数据写入Redis
判断两者是否相互喜欢
如果相互喜欢,完成好友添加
3、Redis中的操作
3. TanHuaController
/*** 喜欢*/
@GetMapping("{id}/love")
public ResponseEntity<Void> likeUser(@PathVariable("id") Long likeUserId) {this.tanhuaService.likeUser(likeUserId);return ResponseEntity.ok(null);
}/*** 不喜欢*/
@GetMapping("{id}/unlove")
public ResponseEntity<Void> notLikeUser(@PathVariable("id") Long likeUserId) {this.tanhuaService.notLikeUser(likeUserId);return ResponseEntity.ok(null);
}
4. TanHuaService
@Autowiredprivate MessagesService messagesService;//探花喜欢 106 - 2public void likeUser(Long likeUserId) {//1、调用API,保存喜欢数据(保存到MongoDB中)Boolean save = userLikeApi.saveOrUpdate(UserHolder.getUserId(),likeUserId,true);if(!save) {//失败throw new BusinessException(ErrorResult.error());}//2、操作redis,写入喜欢的数据,删除不喜欢的数据 (喜欢的集合,不喜欢的集合)
redisTemplate.opsForSet().remove(Constants.USER_NOT_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString());redisTemplate.opsForSet().add(Constants.USER_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString());//3、判断是否双向喜欢if(isLike(likeUserId,UserHolder.getUserId())) {//4、添加好友messagesService.contacts(likeUserId);}}public Boolean isLike(Long userId,Long likeUserId) {String key = Constants.USER_LIKE_KEY+userId;return redisTemplate.opsForSet().isMember(key,likeUserId.toString());}//不喜欢public void notLikeUser(Long likeUserId) {//1、调用API,保存喜欢数据(保存到MongoDB中)Boolean save = userLikeApi.saveOrUpdate(UserHolder.getUserId(),likeUserId,false);if(!save) {//失败throw new BusinessException(ErrorResult.error());}//2、操作redis,写入喜欢的数据,删除不喜欢的数据 (喜欢的集合,不喜欢的集合)redisTemplate.opsForSet().add(Constants.USER_NOT_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString());redisTemplate.opsForSet().remove(Constants.USER_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString());//3、判断是否双向喜欢,删除好友(各位自行实现)}
5. API和实现类
public interface UserLikeApi {//保存或者更新Boolean saveOrUpdate(Long userId, Long likeUserId, boolean isLike);
}
@DubboService
public class UserLikeApiImpl implements UserLikeApi{@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic Boolean saveOrUpdate(Long userId, Long likeUserId, boolean isLike) {try {//1、查询数据Query query = Query.query(Criteria.where("userId").is(userId).and("likeUserId").is(likeUserId));UserLike userLike = mongoTemplate.findOne(query, UserLike.class);//2、如果不存在,保存if(userLike == null) {userLike = new UserLike();userLike.setUserId(userId);userLike.setLikeUserId(likeUserId);userLike.setCreated(System.currentTimeMillis());userLike.setUpdated(System.currentTimeMillis());userLike.setIsLike(isLike);mongoTemplate.save(userLike);}else {//3、更新Update update = Update.update("isLike", isLike).set("updated",System.currentTimeMillis());mongoTemplate.updateFirst(query,update,UserLike.class);}return true;} catch (Exception e) {e.printStackTrace();return false;}}
}
二. MongoDB地理位置检索
2-1 地理位置索引
随着互联网5G网络的发展, 定位技术越来越精确,地理位置的服务(Location Based Services,LBS)已经渗透到各个软件应用中。如网约车平台,外卖,社交软件,物流等。
目前很多数据库都支持地理位置检索服务,如:MySQL,MongoDB, Elasticsearch等。我们的课程使用MongoDB实现。MongoDB 支持对地理空间数据的查询操作。
在MongoDB中使用地理位置查询,必须创建索引才能使用,其内部支持两种索引类型
- 2d
支持二维平面的地理位置数据运算 , 能够将数据作为二维平面上的点存储起来
- 2dsphere
支持类地球的球面上进行几何计算 , 以GeoJSON对象或者普通坐标对的方式存储数据。
MongoDB内部支持多种GeoJson对象类型:
Point
最基础的坐标点,指定纬度和经度坐标,首先列出经度,然后列出 纬度:
- 有效的经度值介于
-180
和之间180
,两者都包括在内。 - 有效的纬度值介于
-90
和之间90
,两者都包括在内。
{ type: "Point", coordinates: [ 40, 5 ] }
LineString
{ type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
Polygon
{type: "Polygon",coordinates: [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ] ]
}
2-2 案例
查询附近并按照距离返回
1. 环境准备
1、创建数据库
2、创建索引
3、导入数据
4、配置实体类
GeoJsonPoint : 地理位置坐标(经纬度)
2. 查询附近
查询当前坐标附近的目标
@Test
public void testNear() {//构造坐标点 (经度 - 纬度)GeoJsonPoint point = new GeoJsonPoint(116.404, 39.915);//构造半径 (距离 - 距离单位)Distance distanceObj = new Distance(1, Metrics.KILOMETERS);//画了一个圆圈(圆点)Circle circle = new Circle(point, distanceObj);//构造query对象Query query = Query.query(Criteria.where("location").withinSphere(circle));//查询List<Places> list = mongoTemplate.find(query, Places.class);list.forEach(System.out::println);
}
3. 查询并获取距离
我们假设需要以当前坐标为原点,查询附近指定范围内的餐厅,并直接显示距离
//查询附近且获取间距
@Test
public void testNear1() {//1、构造中心点(圆点)GeoJsonPoint point = new GeoJsonPoint(116.404, 39.915);//2、构建NearQuery对象NearQuery query = NearQuery.near(point, Metrics.KILOMETERS).maxDistance(1, Metrics.KILOMETERS);//3、调用mongoTemplate的geoNear方法查询GeoResults<Places> results = mongoTemplate.geoNear(query, Places.class);//4、解析GeoResult对象,获取距离和数据for (GeoResult<Places> result : results) {Places places = result.getContent();double value = result.getDistance().getValue();System.out.println(places+"---距离:"+value + "km");}
}
geoNear方法是根据间距排序的
总结
MongoDB地理位置配置索引,推荐使用2dsphere
实体类使用GeoJsonPoint指定地理位置(经纬度)
查询附近使用MongoTemplate的withinSphere方法
查询并获取距离使用geoNear方法
三. 附近的人
3-1 上报地理位置
当客户端检测用户的地理位置,当变化大于500米时或每隔5分钟,向服务端上报地理位置。
用户的地理位置存储到MongoDB中,如下:
1. 执行流程
2. 表结构
user_location表中已经指定location字段索引类型: 2DSphere
对于同一个用户,只有一个地理位置数据
3. 思路分析
思考:如何获取并上报地理位置?
客户端定位获取地理位置信息
客户端定时发送定位数据(5分钟)
客户端检测移动距离发送定位数据(大于500米)
需求:实现上报地理位置功能(首次上报保存数据,后续上报更新数据)
注意事项: GeoJsonPoint对象不支持序列化 ( 在消费者和提供者直接不能传递GeoJsonPoint对象 )
代码步骤:
搭建提供者环境
编写Controller接受请求参数
编写Service调用API完成上报地理位置功能
在API层完成更新或者保存操作
4. 接口文档
5. 定义pojo
在my-tanhua-dubbo-interface中创建:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "user_location")
@CompoundIndex(name = "location_index", def = "{'location': '2dsphere'}")
public class UserLocation implements java.io.Serializable{private static final long serialVersionUID = 4508868382007529970L;@Idprivate ObjectId id;@Indexedprivate Long userId; //用户idprivate GeoJsonPoint location; //x:经度 y:纬度private String address; //位置描述private Long created; //创建时间private Long updated; //更新时间private Long lastUpdated; //上次更新时间
}
6. BaiduController
@RestController
@RequestMapping("/baidu")
public class BaiduController {@Autowiredprivate BaiduService baiduService;/*** 更新位置*/@PostMapping("/location")public ResponseEntity updateLocation(@RequestBody Map param) {Double longitude = Double.valueOf(param.get("longitude").toString());Double latitude = Double.valueOf(param.get("latitude").toString());String address = param.get("addrStr").toString();this.baiduService.updateLocation(longitude, latitude,address);return ResponseEntity.ok(null);}
}
7. BaiduService
@Service
public class BaiduService {@DubboReferenceprivate UserLocationApi userLocationApi;//更新地理位置public void updateLocation(Double longitude, Double latitude, String address) {Boolean flag = userLocationApi.updateLocation(UserHolder.getUserId(),longitude,latitude,address);if(!flag) {throw new BusinessException(ErrorResult.error());}}
}
8. API实现
在my-tanhua-dubbo-interface工程中。
public interface UserLocationApi {//更新地理位置Boolean updateLocation(Long userId, Double longitude, Double latitude, String address);
}
UserLocationApiImpl
@DubboService
public class UserLocationApiImpl implements UserLocationApi{@Autowiredprivate MongoTemplate mongoTemplate;//更新地理位置public Boolean updateLocation(Long userId, Double longitude, Double latitude, String address) {try {//1、根据用户id查询位置信息Query query = Query.query(Criteria.where("userId").is(userId));UserLocation location = mongoTemplate.findOne(query, UserLocation.class);if(location == null) {//2、如果不存在用户位置信息,保存location = new UserLocation();location.setUserId(userId);location.setAddress(address);location.setCreated(System.currentTimeMillis());location.setUpdated(System.currentTimeMillis());location.setLastUpdated(System.currentTimeMillis());location.setLocation(new GeoJsonPoint(longitude,latitude));mongoTemplate.save(location);}else {//3、如果存在,更新Update update = Update.update("location", new GeoJsonPoint(longitude, latitude)).set("updated", System.currentTimeMillis()).set("lastUpdated", location.getUpdated());mongoTemplate.updateFirst(query,update,UserLocation.class);}return true;} catch (Exception e) {e.printStackTrace();return false;}}
}
3-2 搜附近
在首页中点击“搜附近”可以搜索附近的好友,效果如下:
实现思路:根据当前用户的位置,查询附近范围内的用户。范围是可以设置的。
1. 接口文档
2. 实现步骤
3. NearUserVo
//附近的人vo对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NearUserVo {private Long userId;private String avatar;private String nickname;public static NearUserVo init(UserInfo userInfo) {NearUserVo vo = new NearUserVo();vo.setUserId(userInfo.getId());vo.setAvatar(userInfo.getAvatar());vo.setNickname(userInfo.getNickname());return vo;}
}
4. TanHuaController
/*** 搜附近*/@GetMapping("/search")public ResponseEntity<List<NearUserVo>> queryNearUser(String gender,@RequestParam(defaultValue = "2000") String distance) {List<NearUserVo> list = this.tanhuaService.queryNearUser(gender, distance);return ResponseEntity.ok(list);}
5. TanHuaService
//搜附近public List<NearUserVo> queryNearUser(String gender, String distance) {//1、调用API查询附近的用户(返回的是附近的人的所有用户id,包含当前用户的id)List<Long> userIds = userLocationApi.queryNearUser(UserHolder.getUserId(),Double.valueOf(distance));//2、判断集合是否为空if(CollUtil.isEmpty(userIds)) {return new ArrayList<>();}//3、调用UserInfoApi根据用户id查询用户详情UserInfo userInfo = new UserInfo();userInfo.setGender(gender);Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, userInfo);//4、构造返回值。List<NearUserVo> vos = new ArrayList<>();for (Long userId : userIds) {//排除当前用户if(userId == UserHolder.getUserId()) {continue;}UserInfo info = map.get(userId);if(info != null) {NearUserVo vo = NearUserVo.init(info);vos.add(vo);}}return vos;}
6. API实现
UserLocationApi
/*** 根据位置搜索附近人的所有ID*/List<Long> queryNearUser(Long userId, Double metre);
UserLocationApiImpl
@Overridepublic List<Long> queryNearUser(Long userId, Double metre) {//1、根据用户id,查询用户的位置信息Query query = Query.query(Criteria.where("userId").is(userId));UserLocation location = mongoTemplate.findOne(query, UserLocation.class);if(location == null) {return null;}//2、已当前用户位置绘制原点GeoJsonPoint point = location.getLocation();//3、绘制半径Distance distance = new Distance(metre / 1000, Metrics.KILOMETERS);//4、绘制圆形Circle circle = new Circle(point, distance);//5、查询Query locationQuery = Query.query(Criteria.where("location").withinSphere(circle));List<UserLocation> list = mongoTemplate.find(locationQuery, UserLocation.class);return CollUtil.getFieldValues(list,"userId",Long.class);}
七(7)探花功能-MongoDB地理位置查询-附近的人相关推荐
- 详解基于MongoDB的地理位置查询,结合Symfony2演示
简介 随着近几年各类移动终端的迅速普及,基于地理位置的服务(LBS)和相关应用也越来越多,而支撑这些应用的最基础技术之一,就是基于地理位置信息的处理.我所在的项目也正从事相关系统的开发,我们使用的是S ...
- java mongodb 模糊查询_Java操作MongoDB插入数据进行模糊查询与in查询功能的方法
Java操作MongoDB插入数据进行模糊查询与in查询功能 由于需要用MongoDB缓存数据,所以自己写了一套公共的存放和读取方法 具体如下: 存放mongodb: /** * 公共方法:设置Obj ...
- java 实现查询近七天数据功能
java 实现查询近七天数据功能 接上一篇 如何使用echarts表图地址 实现了页面的表图 那么如何对接数据 如何使用 耐心看完!!! 这次就以右下角这一个表图做示范 这个表图的下面是按时间排序的 ...
- Spring MongoDB查询附近的人功能实现
Spring MongoDB简易实现查询附近的人功能 文章目录 1.准备 2.搭建基础结构并编写代码 3.测试接口 1.分别存入3位用户 2.测试使用id查用户 3.使用广东博物馆西门的坐标测试附近有 ...
- 23.MongoDB地理位置检索
MongoDB地理位置检索 一.查询当前坐标附近的目标 @Testpublic void queryNear(){//1.以当前位置的经纬度为圆点GeoJsonPoint point = new Ge ...
- 探花交友_第8章_搜附近以及探花功能实现
探花交友_第8章_搜附近以及探花功能实现 文章目录 探花交友_第8章_搜附近以及探花功能实现 1.上报地理位置 1.1.dubbo服务 1.1.1.创建工程 1.1.2.定义pojo 1.1.3.定义 ...
- MongoDB聚合查询 Pipeline 和 MapReduce
MongoDB聚合查询 MongoDB聚合查询 什么是聚合查询 Pipeline聚合管道方法 聚合流程 详细流程 聚合语法 常用聚合管道 $count $group $match $unwind $p ...
- mongodb模糊查询 php7_详解php7如何实现MongoDB模糊查询
php7如何实现MongoDB模糊查询?MongoDB模糊查询语句相信对大家来说都不陌生,本文主要给大家介绍了在php 7中MongoDB实现模糊查询的方法,文中给出了详细的介绍和示例代码,对大家具有 ...
- mongdb mysql geospatial 比较_MongoDB的地理位置查询,以及和mysql的使用对比
MongoDB的一个特色就是具有丰富的查询接口,比如地理位置查询. 在地理位置查询上,MongoDB有着比传统关系型数据库的优势,下面举个例子. 当前移动互联网应用,按用户离目标门店距离排序上的场景很 ...
最新文章
- 1.Socket通信
- android ndk怎样加载o文件_JNI初探之NDK 开发环境配置
- 基于SSM实现宠物商城系统
- 计算机网络期中考试总结反思,期中考试总结反思作文(通用6篇)
- Web前端规范--HTML规范
- conda init 关闭和重启shell_TP5实战源码 通过shell建立PHP守护程序
- ssh配置文件ssh_config和sshd_config区别
- 信息技术与计算机文化的问题,信息技术与计算机文化
- java 布尔逻辑运算符_Java运算符
- 信息学奥赛C++语言: 统计闰年
- 个人博客四|注册登录退出功能后台开发
- jQuery插件实例二:年华时代插件ReturnTop回到首页
- html实现画板的基本操作,JavaScript操作Canvas实现画板实例分析
- 风吹雨PHP多应用授权系统【开源】
- Team Foundation Server 2013 with Update 3 Install LOG
- ST语言入门(维修电工1)
- std::numeric_limits使用
- PTA---计算存款利息 (10 分)
- MT【18】幂平均不等式的证明
- Google的OR-Tools