> 文章列表 > 【探花交友】day07—搜附近

【探花交友】day07—搜附近

【探花交友】day07—搜附近

目录

课程说明

1、探花

1.1、查询推荐列表dubbo服务

1.2、查询推荐列表APP接口实现

1.3、喜欢的dubbo服务

1.4、左滑右滑

2、MongoDB地理位置检索

2.1、地理位置索引

2.2、案例

3、上报地理位置

3.1、dubbo服务

3.2、APP接口

3.3、测试

4、搜附近

4.1、dubbo服务

4.2、APP接口服务

课程说明

  • 实现探花功能

  • MongoDB geo

  • 搜附近

    • 上报地理位置

    • 搜附近

1、探花

探花功能是将推荐的好友随机的通过卡片的形式展现出来,用户可以选择左滑、右滑操作,左滑:“不喜欢”,右滑:“喜欢”。

喜欢:如果双方喜欢,那么就会成为好友。

如果已经喜欢或不喜欢的用户在列表中不再显示。

1.1、查询推荐列表dubbo服务

1.1.1、实体对象

package com.tanhua.dubbo.server.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;@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; // 更新时间}

1.1.2、定义接口

RecommendUserApi

 /* 查询探花列表,查询时需要排除喜欢和不喜欢的用户*/List<RecommendUser> queryCardsList(Long userId, int count);

1.1.3、编写实现

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.1.4、单元测试

//com.tanhua.dubbo.server.api.TestRecommendUserApi@Test
public void testQueryCardList(){this.recommendUserApi.queryCardsList(106l, 10).forEach(recommendUser -> System.out.println(recommendUser));
}

1.2、查询推荐列表APP接口实现

接口文档:https://mock-java.itheima.net/project/35/interface/api/593

1.2.1、TanHuaController

/* 探花-推荐用户列表*/@GetMapping("/cards")public ResponseEntity queryCardsList() {List<TodayBest> list = this.tanhuaService.queryCardsList();return ResponseEntity.ok(list);}

1.2.2、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;}

1.2.3、测试

效果:

1.3、喜欢的dubbo服务

用户的喜欢与不喜欢列表需要保存在redis中,为了防止redis中的数据丢失,同时需要将数据保存到mongodb进行持久化保存。

1.3.1、定义接口

package com.tanhua.dubbo.api;public interface UserLikeApi {//保存或者更新Boolean saveOrUpdate(Long userId, Long likeUserId, boolean isLike);
}

1.3.2、编写实现

@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;}}
}

1.4、左滑右滑

左滑:“不喜欢”,右滑:“喜欢”,如果双方喜欢,那么就会成为好友。

1.4.1、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);
}

1.4.2、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、判断是否双向喜欢,删除好友(各位自行实现)}

1.4.3、测试

user_like表,可以看到已经相互喜欢了:

tanhua_users表,可以看到相互是好友了:

环信平台:

2、MongoDB地理位置检索

MongoDB 支持对地理空间数据的查询操作。

2.1、地理位置索引

地理位置查询,必须创建索引才可以能查询,目前有两种索引。

2d :

使用2d index 能够将数据作为二维平面上的点存储起来,在MongoDB 2.4以前使用2。

2dsphere:

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、案例

查询附近并按照距离返回

查询附近

查询当前坐标附近的目标

@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);
}

查询并获取距离

我们假设需要以当前坐标为原点,查询附近指定范围内的餐厅,并直接显示距离

//查询附近且获取间距
@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");}
}

3、上报地理位置

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

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

3.1、dubbo服务

3.1.1、定义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; //上次更新时间
}
3.1.2、定义dubbo接口

在my-tanhua-dubbo-interface工程中。

package com.tanhua.dubbo.server.api;public interface UserLocationApi {//更新地理位置Boolean updateLocation(Long userId, Double longitude, Double latitude, String address);
}

3.1.3、编写实现

@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.1.4、单元测试

地理位置坐标拾取:拾取坐标系统

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class TestUserLocationApi {@DubboReferenceprivate UserLocationApi userLocationApi;@Testpublic void testUpdateUserLocation() {this.userLocationApi.updateLocation(1L, 116.353885,40.065911, "育新地铁站");this.userLocationApi.updateLocation(2L, 116.352115,40.067441, "北京石油管理干部学院");this.userLocationApi.updateLocation(3L, 116.336438,40.072505, "回龙观医院");this.userLocationApi.updateLocation(4L, 116.396797,40.025231, "奥林匹克森林公园");this.userLocationApi.updateLocation(5L, 116.323849,40.053723, "小米科技园");this.userLocationApi.updateLocation(6L, 116.403963,39.915119, "天安门");this.userLocationApi.updateLocation(7L, 116.328103,39.900835, "北京西站");this.userLocationApi.updateLocation(8L, 116.609564,40.083812, "北京首都国际机场");this.userLocationApi.updateLocation(9L, 116.459958,39.937193, "德云社(三里屯店)");this.userLocationApi.updateLocation(10L, 116.333374,40.009645, "清华大学");this.userLocationApi.updateLocation(41L, 116.316833,39.998877, "北京大学");this.userLocationApi.updateLocation(42L, 117.180115,39.116464, "天津大学(卫津路校区)");}
}

3.2、APP接口

接口文档:https://mock-java.itheima.net/project/35/interface/api/557

3.2.1、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);}
}

3.2.2、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());}}
}

3.3、测试

4、搜附近

在首页中点击“搜附近”可以搜索附近的好友,效果如下:

实现思路:根据当前用户的位置,查询附近范围内的用户。范围是可以设置的。

4.1、dubbo服务

4.1.1、定义接口方法

UserLocationApi

 /* 根据位置搜索附近人的所有ID*/List<Long> queryNearUser(Long userId, Double metre);

4.1.2、编写实现

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);}

4.2、APP接口服务

4.2.1、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.2.2、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);}

4.2.3、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;}

4.2.4、测试