1、探花
探花功能是将推荐的好友随机的通过卡片的形式展现出来,用户可以选择左滑、右滑操作,左滑:“不喜欢”,右滑:“喜欢”。
喜欢:如果双方喜欢,那么就会成为好友。

如果已经喜欢或不喜欢的用户在列表中不再显示。
对于喜欢/不喜欢功能,使用Redis中的set进行存储。Redis的Set是无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。使用Set可以方便的实现交集,并集等个性化查询。


1.1、查询推荐列表dubbo服务
数据库表:

1.1.1、实体对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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; @Indexed private Long userId; @Indexed private Long likeUserId; private Boolean isLike; private Long created; private Long updated;
}
|
1.1.2、定义接口
RecommendUserApi
1 2 3 4 5
|
List<RecommendUser> queryCardsList(Long userId, int count);
|
1.1.3、编写实现
RecommendUserApiImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public List<RecommendUser> queryCardsList(Long userId, int counts) { List<UserLike> likeList = mongoTemplate.find(Query.query(Criteria.where("userId").is(userId)), UserLike.class); List<Long> likeUserIdS = CollUtil.getFieldValues(likeList, "likeUserId", Long.class); Criteria criteria = Criteria.where("toUserId").is(userId).and("userId").nin(likeUserIdS); TypedAggregation<RecommendUser> newAggregation = TypedAggregation.newAggregation(RecommendUser.class, Aggregation.match(criteria), Aggregation.sample(counts) ); AggregationResults<RecommendUser> results = mongoTemplate.aggregate(newAggregation, RecommendUser.class); return results.getMappedResults(); }
|
1.1.4、单元测试
1 2 3 4 5 6 7
|
@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

每次查询时随机返回10条推荐用户的数据,需要排除已喜欢/不喜欢的数据。
基本思路:
1、查询已喜欢/不喜欢的用户列表
2、查询推荐列表,并排除已喜欢/不喜欢用户
3、如果推荐列表不存在,构造默认数据‘

1.2.1、TanHuaController
1 2 3 4 5 6 7 8
|
@GetMapping("/cards") public ResponseEntity queryCardsList() { List<TodayBest> list = this.tanhuaService.queryCardsList(); return ResponseEntity.ok(list); }
|
1.2.2、TanHuaService
1 2 3 4 5
| tanhua: default: recommend: users: 2,3,8,10,18,20,24,29,27,32,36,37,56,64,75,88
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Value("${tanhua.default.recommend.users}") private String recommendUser;
public List<TodayBest> queryCardsList() { List<RecommendUser> users = recommendUserApi.queryCardsList(UserHolder.getUserId(),10); if(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); } } List<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、保存喜欢数据(数据存入MogoDB,同时处理redis缓存)
Redis中通过两个集合描述喜欢/不喜欢的数据
USER_LIKE_SET_106: 1,2,3,4,5,6
USER_NOT_LlikE_SET_106 : 7,8
判断对方是否也喜欢我
如果双方互相喜欢,添加好友关系:

1、编写API层方法,将喜欢数据存入MongoDB
- 查询是否存在喜欢数据
- 如果存在更新数据,如果不存在保存数据
2、Service层进行代码调用
- 调用API层保存喜欢数据,喜欢数据写入Redis
- 判断两者是否相互喜欢
- 如果相互喜欢,完成好友添加
3、Redis中的操作

1.3.1、定义接口
1 2 3 4 5 6 7 8
| package com.tanhua.dubbo.api;
public interface UserLikeApi {
Boolean saveOrUpdate(Long userId, Long likeUserId, boolean isLike); }
|
1.3.2、编写实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @DubboService public class UserLikeApiImpl implements UserLikeApi{
@Autowired private MongoTemplate mongoTemplate;
@Override public Boolean saveOrUpdate(Long userId, Long likeUserId, boolean isLike) { try { Query query = Query.query(Criteria.where("userId").is(userId).and("likeUserId").is(likeUserId)); UserLike userLike = mongoTemplate.findOne(query, UserLike.class); 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 { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Autowired private MessagesService messagesService;
public void likeUser(Long likeUserId) { Boolean save = userLikeApi.saveOrUpdate(UserHolder.getUserId(),likeUserId,true); if(!save) { throw new BusinessException(ErrorResult.error()); } redisTemplate.opsForSet().remove(Constants.USER_NOT_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString()); redisTemplate.opsForSet().add(Constants.USER_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString()); if(isLike(likeUserId,UserHolder.getUserId())) { 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) { Boolean save = userLikeApi.saveOrUpdate(UserHolder.getUserId(),likeUserId,false); if(!save) { throw new BusinessException(ErrorResult.error()); } redisTemplate.opsForSet().add(Constants.USER_NOT_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString()); redisTemplate.opsForSet().remove(Constants.USER_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString()); }
|
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
,两者都包括在内。
1
| { type: "Point", coordinates: [ 40, 5 ] }
|
LineString
1
| { type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
|
Polygon
1 2 3 4
| { type: "Polygon", coordinates: [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ] ] }
|

2.2、案例
查询附近并按照距离返回
查询附近
查询当前坐标附近的目标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @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(Criteria.where("location").withinSphere(circle)); List<Places> list = mongoTemplate.find(query, Places.class); list.forEach(System.out::println); }
|
查询并获取距离
我们假设需要以当前坐标为原点,查询附近指定范围内的餐厅,并直接显示距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test public void testNear1() { GeoJsonPoint point = new GeoJsonPoint(116.404, 39.915); NearQuery query = NearQuery.near(point, Metrics.KILOMETERS).maxDistance(1, Metrics.KILOMETERS); GeoResults<Places> results = mongoTemplate.geoNear(query, Places.class); for (GeoResult<Places> result : results) { Places places = result.getContent(); double value = result.getDistance().getValue(); System.out.println(places+"---距离:"+value + "km"); } }
|

3、上报地理位置
当客户端检测用户的地理位置,当变化大于500米时或每隔5分钟,向服务端上报地理位置。
用户的地理位置存储到MongoDB中,如下:


表结构:(user_location)

如何获取并上报地理位置:
- 客户端定位获取地理位置信息
- 客户端定时发送定位数据
- 客户端检测移动距离发送定位数据(大于500米)

实现上报功能位置(首次上报数据保存,后期进行更新)
3.1、dubbo服务
3.1.1、定义pojo

在my-tanhua-dubbo-interface中创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @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;
@Id private ObjectId id; @Indexed private Long userId; private GeoJsonPoint location; private String address; private Long created; private Long updated; private Long lastUpdated; }
|
3.1.2、定义dubbo接口
在my-tanhua-dubbo-interface工程中。
1 2 3 4 5 6 7 8
| package com.tanhua.dubbo.server.api;
public interface UserLocationApi {
Boolean updateLocation(Long userId, Double longitude, Double latitude, String address); }
|
3.1.3、编写实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @DubboService public class UserLocationApiImpl implements UserLocationApi{
@Autowired private MongoTemplate mongoTemplate;
public Boolean updateLocation(Long userId, Double longitude, Double latitude, String address) { try { Query query = Query.query(Criteria.where("userId").is(userId)); UserLocation location = mongoTemplate.findOne(query, UserLocation.class); if(location == null) { 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 { 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、单元测试
地理位置坐标拾取:http://api.map.baidu.com/lbsapi/getpoint/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @RunWith(SpringRunner.class) @SpringBootTest(classes = AppServerApplication.class) public class TestUserLocationApi {
@DubboReference private UserLocationApi userLocationApi;
@Test public 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RestController @RequestMapping("/baidu") public class BaiduController {
@Autowired private 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Service public class BaiduService {
@DubboReference private 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
1 2 3 4 5
|
List<Long> queryNearUser(Long userId, Double metre);
|
4.1.2、编写实现
UserLocationApiImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Override public List<Long> queryNearUser(Long userId, Double metre) { Query query = Query.query(Criteria.where("userId").is(userId)); UserLocation location = mongoTemplate.findOne(query, UserLocation.class); if(location == null) { return null; } GeoJsonPoint point = location.getLocation(); Distance distance = new Distance(metre / 1000, Metrics.KILOMETERS); Circle circle = new Circle(point, distance); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @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
1 2 3 4 5 6 7 8 9
|
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public List<NearUserVo> queryNearUser(String gender, String distance) { List<Long> userIds = userLocationApi.queryNearUser(UserHolder.getUserId(),Double.valueOf(distance)); if(CollUtil.isEmpty(userIds)) { return new ArrayList<>(); } UserInfo userInfo = new UserInfo(); userInfo.setGender(gender); Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, userInfo); 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、测试


1.上报地理位置
2.搜索附近

