课程总结

1.探花功能

  1. 业务需求

  2. 执行过程

2.MongoDB的地理位置查询

  1. 地理位置查询的应用场景

  2. 查询案例

3.搜附近

  1. 上报地理位置

  2. 使用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方法是根据间距排序的

总结

  1. MongoDB地理位置配置索引,推荐使用2dsphere

  2. 实体类使用GeoJsonPoint指定地理位置(经纬度)

  3. 查询附近使用MongoTemplate的withinSphere方法

  4. 查询并获取距离使用geoNear方法

三. 附近的人

3-1 上报地理位置

当客户端检测用户的地理位置,当变化大于500米时或每隔5分钟,向服务端上报地理位置。

用户的地理位置存储到MongoDB中,如下:

1. 执行流程

2. 表结构

user_location表中已经指定location字段索引类型: 2DSphere

对于同一个用户,只有一个地理位置数据

3. 思路分析

思考:如何获取并上报地理位置?

  1. 客户端定位获取地理位置信息

  2. 客户端定时发送定位数据(5分钟)

  3. 客户端检测移动距离发送定位数据(大于500米)

需求:实现上报地理位置功能(首次上报保存数据,后续上报更新数据)

注意事项: GeoJsonPoint对象不支持序列化 ( 在消费者和提供者直接不能传递GeoJsonPoint对象 )

代码步骤:

  1. 搭建提供者环境

  2. 编写Controller接受请求参数

  3. 编写Service调用API完成上报地理位置功能

  4. 在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地理位置查询-附近的人相关推荐

  1. 详解基于MongoDB的地理位置查询,结合Symfony2演示

    简介 随着近几年各类移动终端的迅速普及,基于地理位置的服务(LBS)和相关应用也越来越多,而支撑这些应用的最基础技术之一,就是基于地理位置信息的处理.我所在的项目也正从事相关系统的开发,我们使用的是S ...

  2. java mongodb 模糊查询_Java操作MongoDB插入数据进行模糊查询与in查询功能的方法

    Java操作MongoDB插入数据进行模糊查询与in查询功能 由于需要用MongoDB缓存数据,所以自己写了一套公共的存放和读取方法 具体如下: 存放mongodb: /** * 公共方法:设置Obj ...

  3. java 实现查询近七天数据功能

    java 实现查询近七天数据功能 接上一篇 如何使用echarts表图地址 实现了页面的表图 那么如何对接数据 如何使用 耐心看完!!! 这次就以右下角这一个表图做示范 这个表图的下面是按时间排序的 ...

  4. Spring MongoDB查询附近的人功能实现

    Spring MongoDB简易实现查询附近的人功能 文章目录 1.准备 2.搭建基础结构并编写代码 3.测试接口 1.分别存入3位用户 2.测试使用id查用户 3.使用广东博物馆西门的坐标测试附近有 ...

  5. 23.MongoDB地理位置检索

    MongoDB地理位置检索 一.查询当前坐标附近的目标 @Testpublic void queryNear(){//1.以当前位置的经纬度为圆点GeoJsonPoint point = new Ge ...

  6. 探花交友_第8章_搜附近以及探花功能实现

    探花交友_第8章_搜附近以及探花功能实现 文章目录 探花交友_第8章_搜附近以及探花功能实现 1.上报地理位置 1.1.dubbo服务 1.1.1.创建工程 1.1.2.定义pojo 1.1.3.定义 ...

  7. MongoDB聚合查询 Pipeline 和 MapReduce

    MongoDB聚合查询 MongoDB聚合查询 什么是聚合查询 Pipeline聚合管道方法 聚合流程 详细流程 聚合语法 常用聚合管道 $count $group $match $unwind $p ...

  8. mongodb模糊查询 php7_详解php7如何实现MongoDB模糊查询

    php7如何实现MongoDB模糊查询?MongoDB模糊查询语句相信对大家来说都不陌生,本文主要给大家介绍了在php 7中MongoDB实现模糊查询的方法,文中给出了详细的介绍和示例代码,对大家具有 ...

  9. mongdb mysql geospatial 比较_MongoDB的地理位置查询,以及和mysql的使用对比

    MongoDB的一个特色就是具有丰富的查询接口,比如地理位置查询. 在地理位置查询上,MongoDB有着比传统关系型数据库的优势,下面举个例子. 当前移动互联网应用,按用户离目标门店距离排序上的场景很 ...

最新文章

  1. 1.Socket通信
  2. android ndk怎样加载o文件_JNI初探之NDK 开发环境配置
  3. 基于SSM实现宠物商城系统
  4. 计算机网络期中考试总结反思,期中考试总结反思作文(通用6篇)
  5. Web前端规范--HTML规范
  6. conda init 关闭和重启shell_TP5实战源码 通过shell建立PHP守护程序
  7. ssh配置文件ssh_config和sshd_config区别
  8. 信息技术与计算机文化的问题,信息技术与计算机文化
  9. java 布尔逻辑运算符_Java运算符
  10. 信息学奥赛C++语言: 统计闰年
  11. 个人博客四|注册登录退出功能后台开发
  12. jQuery插件实例二:年华时代插件ReturnTop回到首页
  13. html实现画板的基本操作,JavaScript操作Canvas实现画板实例分析
  14. 风吹雨PHP多应用授权系统【开源】
  15. Team Foundation Server 2013 with Update 3 Install LOG
  16. ST语言入门(维修电工1)
  17. std::numeric_limits使用
  18. PTA---计算存款利息 (10 分)
  19. MT【18】幂平均不等式的证明
  20. Google的OR-Tools

热门文章

  1. GitHub统计代码量
  2. esp32~mp3播放实例解析
  3. Android逆向之frida-dexdump脱壳分析某肿瘤sign
  4. 马云语录:骗子、疯子、狂人?
  5. 普通职场人,35岁以后失业怎么办?这是我见过最真诚的建议
  6. centios修改盘符
  7. python 小说 小说_我用Python实现了一个小说网站雏形
  8. Python从入门到熟练(2):Python简介
  9. 高项 12 项目采购管理
  10. QT开发环境的建立以及qte4.6.3、tislib1.4移植到博创star2410开发板