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.搜索附近

