探花交友_第8章_搜附近以及探花功能实现
探花交友_第8章_搜附近以及探花功能实现
文章目录
- 探花交友_第8章_搜附近以及探花功能实现
- 1、上报地理位置
- 1.1、dubbo服务
- 1.1.1、创建工程
- 1.1.2、定义pojo
- 1.1.3、定义dubbo接口
- 1.1.4、编写实现
- 1.1.5、单元测试
- 1.2、APP接口
- 1.2.1、BaiduController
- 1.2.2、BaiduService
- 1.3、测试
- 在这里插入图片描述
- 2、搜附近
- 2.1、dubbo服务
- 2.1.1、定义接口方法
- 2.1.2、编写实现
- 2.1.3、单元测试
- 2.2、APP接口服务
- 2.2.1、NearUserVo
- 2.2.2、TanHuaController
- 2.2.3、TanHuaService
- 2.2.4、测试
- 3、探花
- 3.1、喜欢的dubbo服务
- 3.1.1、实体对象
- 3.1.2、定义接口
- 3.1.3、编写实现
- 3.1.4、单元测试
- 3.2、查询推荐列表dubbo服务
- 3.2.1、定义接口
- 3.2.2、编写实现
- 3.2.3、单元测试
- 3.3、查询推荐列表APP接口实现
- 3.3.1、TanHuaController
- 3.3.2、TanHuaService
- 3.3.3、测试
- 3.4、左滑右滑
- 3.4.1、TanHuaController
- 3.4.2、TanHuaService
- 3.4.3、测试
- 4、用户资料
- 4.1、基本信息
- 4.4.1、接口信息
- 在这里插入图片描述
- 4.4.2、MyCenterController
- 4.4.3、MyCenterService
- 4.4.4、UserInfoService
- 4.4.5、bug修复
- 4.2、更新头像
- 4.2.2、MyCenterController
- 上报地位位置
- 实现搜附近功能
- 实现探花功能
- 用户基本信息维护
1、上报地理位置
当客户端检测用户的地理位置,当变化大于500米时或每隔5分钟,向服务端上报地理位置。
用户的地理位置存储到Elasticsearch中,需要使用环境提供的ES集群,如下:
1.1、dubbo服务
用户地理位置的服务独立一个新的工程来实现,名字为:my-tanhua-dubbo-es。
1.1.1、创建工程
pom.ml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>my-tanhua-dubbo</artifactId><groupId>cn.itcast.tanhua</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>my-tanhua-dubbo-es</artifactId><dependencies><!--引入interface依赖--><dependency><groupId>cn.itcast.tanhua</groupId><artifactId>my-tanhua-dubbo-interface</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><!--dubbo的springboot支持--><dependency><groupId>com.alibaba.boot</groupId><artifactId>dubbo-spring-boot-starter</artifactId></dependency><!--dubbo框架--><dependency><groupId>com.alibaba</groupId><artifactId>dubbo</artifactId></dependency><!--zk依赖--><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId></dependency><dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency></dependencies>
</project>
application.properties文件:
# Spring boot application
spring.application.name = itcast-tanhua-dubbo-es# dubbo 扫描包配置
dubbo.scan.basePackages = com.tanhua.dubbo.es
dubbo.application.name = dubbo-provider-es#dubbo 对外暴露的端口信息
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20882#dubbo注册中心的配置
dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient
dubbo.registry.timeout = 60000 #ES集群配置
spring.data.elasticsearch.cluster-name=es-tanhua-cluster
spring.data.elasticsearch.cluster-nodes=192.168.31.81:9300,192.168.31.81:9301,192.168.31.81:9302
启动类:
package com.tanhua.dubbo.es;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
public class ESApplication {public static void main(String[] args) {SpringApplication.run(ESApplication.class, args);}
}
1.1.2、定义pojo
在my-tanhua-dubbo-interface中创建:
package com.tanhua.dubbo.server.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)
public class UserLocation {@Idprivate Long userId; //用户id@GeoPointFieldprivate GeoPoint location; //lon:经度 lat:纬度@Field(type = FieldType.Keyword)private String address; //位置描述@Field(type = FieldType.Long)private Long created; //创建时间@Field(type = FieldType.Long)private Long updated; //更新时间@Field(type = FieldType.Long)private Long lastUpdated; //上次更新时间
}
package com.tanhua.dubbo.server.vo;import cn.hutool.core.bean.BeanUtil;
import com.tanhua.dubbo.server.pojo.UserLocation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.List;@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserLocationVo implements java.io.Serializable {private static final long serialVersionUID = 4133419501260037769L;private Long userId; //用户idprivate Double longitude; //经度private Double latitude; //维度private String address; //位置描述private Long created; //创建时间private Long updated; //更新时间private Long lastUpdated; //上次更新时间public static final UserLocationVo format(UserLocation userLocation) {UserLocationVo userLocationVo = BeanUtil.toBean(userLocation, UserLocationVo.class);userLocationVo.setLongitude(userLocation.getLocation().getLon());userLocationVo.setLatitude(userLocation.getLocation().getLat());return userLocationVo;}public static final List<UserLocationVo> formatToList(List<UserLocation> userLocations) {List<UserLocationVo> list = new ArrayList<>();for (UserLocation userLocation : userLocations) {list.add(format(userLocation));}return list;}
}
由于UserLocation不能序列化,所以要再定义UserLocationVo进行返回数据。
在my-tanhua-dubbo-interface中添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId>
</dependency>
1.1.3、定义dubbo接口
在my-tanhua-dubbo-interface工程中。
package com.tanhua.dubbo.server.api;public interface UserLocationApi {/*** 更新用户地理位置** @param userId 用户id* @param longitude 经度* @param latitude 纬度* @param address 地址名称* @return*/Boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address);}
1.1.4、编写实现
package com.tanhua.dubbo.es.api;import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.dubbo.server.pojo.UserLocation;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.*;import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;@Service(version = "1.0.0")
@Slf4j
public class UserLocationApiImpl implements UserLocationApi {@Autowiredprivate ElasticsearchTemplate elasticsearchTemplate;/*** 初始化索引库**/@PostConstructpublic void initIndex(){//判断索引库是否存在,如果不存在,需要创建if(!this.elasticsearchTemplate.indexExists("tanhua")){this.elasticsearchTemplate.createIndex(UserLocation.class);}//判断表是否存在,如果不存在,需要创建if(!this.elasticsearchTemplate.typeExists("tanhua", "user_location")){this.elasticsearchTemplate.putMapping(UserLocation.class);}}@Overridepublic Boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {//查询个人的地理位置数据,如果不存在,需要新增,如果是存在数据,更新数据try {GetQuery getQuery = new GetQuery();getQuery.setId(String.valueOf(userId));UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);if(ObjectUtil.isEmpty(userLocation)){//新增数据userLocation = new UserLocation();userLocation.setUserId(userId);userLocation.setAddress(address);userLocation.setCreated(System.currentTimeMillis());userLocation.setUpdated(userLocation.getCreated());userLocation.setLastUpdated(userLocation.getCreated());userLocation.setLocation(new GeoPoint(latitude, longitude));IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocation).build();//保存数据到ES中this.elasticsearchTemplate.index(indexQuery);}else {//更新数据//更新的字段Map<String,Object> map = new HashMap<>();map.put("location", new GeoPoint(latitude, longitude));map.put("updated", System.currentTimeMillis());map.put("lastUpdated", userLocation.getUpdated());map.put("address", address);UpdateRequest updateRequest = new UpdateRequest();updateRequest.doc(map);UpdateQuery updateQuery = new UpdateQueryBuilder().withId(String.valueOf(userId)).withClass(UserLocation.class).withUpdateRequest(updateRequest).build();//更新数据this.elasticsearchTemplate.update(updateQuery);}return true;} catch (Exception e) {log.error("更新地理位置失败~ userId = " + userId + ", longitude = " + longitude + ", latitude = " + latitude + ", address = " + address, e);}return false;}
}
1.1.5、单元测试
package com.tanhua.dubbo.es;import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.dubbo.server.vo.UserLocationVo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
public class TestUserLocationApi {@Autowiredprivate UserLocationApi userLocationApi;@Testpublic void testUpdateUserLocation() {this.userLocationApi.updateUserLocation(1L, 121.512253, 31.24094, "金茂大厦");this.userLocationApi.updateUserLocation(2L, 121.506377, 31.245105, "东方明珠广播电视塔");this.userLocationApi.updateUserLocation(10L, 121.508815, 31.243844, "陆家嘴地铁站");this.userLocationApi.updateUserLocation(12L, 121.511999, 31.239185, "上海中心大厦");this.userLocationApi.updateUserLocation(25L, 121.493444, 31.240513, "上海市公安局");this.userLocationApi.updateUserLocation(27L, 121.494108, 31.247011, "上海外滩美术馆");this.userLocationApi.updateUserLocation(30L, 121.462452, 31.253463, "上海火车站");this.userLocationApi.updateUserLocation(32L, 121.81509, 31.157478, "上海浦东国际机场");this.userLocationApi.updateUserLocation(34L, 121.327908, 31.20033, "虹桥火车站");this.userLocationApi.updateUserLocation(38L, 121.490155, 31.277476, "鲁迅公园");this.userLocationApi.updateUserLocation(40L, 121.425511, 31.227831, "中山公园");this.userLocationApi.updateUserLocation(43L, 121.594194, 31.207786, "张江高科");}}
1.2、APP接口
接口文档:https://mock-java.itheima.net/project/35/interface/api/557
1.2.1、BaiduController
package com.tanhua.server.controller;import com.tanhua.server.service.BaiduService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;@RestController
@RequestMapping("baidu")
public class BaiduController {@Autowiredprivate BaiduService baiduService;/*** 更新位置** @param param* @return*/@PostMapping("location")public ResponseEntity<Void> updateLocation(@RequestBody Map<String, Object> param) {try {Double longitude = Double.valueOf(param.get("longitude").toString());Double latitude = Double.valueOf(param.get("latitude").toString());String address = param.get("addrStr").toString();Boolean bool = this.baiduService.updateLocation(longitude, latitude, address);if (bool) {return ResponseEntity.ok(null);}} catch (Exception e) {e.printStackTrace();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}
}
1.2.2、BaiduService
package com.tanhua.server.service;import com.alibaba.dubbo.config.annotation.Reference;
import com.tanhua.common.pojo.User;
import com.tanhua.common.utils.UserThreadLocal;
import com.tanhua.dubbo.server.api.UserLocationApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class BaiduService {@Reference(version = "1.0.0")private UserLocationApi userLocationApi;public Boolean updateLocation(Double longitude, Double latitude, String address) {User user = UserThreadLocal.get();try {return this.userLocationApi.updateUserLocation(user.getId(), longitude, latitude, address);} catch (Exception e) {log.error("更新地理位置失败~ userId = " + user.getId() + ", longitude = " + longitude + ", latitude = " + latitude + ", address = " + address, e);}return false;}}
1.3、测试
2、搜附近
在首页中点击“搜附近”可以搜索附近的好友,效果如下:
实现思路:根据当前用户的位置,查询附近范围内的用户。范围是可以设置的。
2.1、dubbo服务
2.1.1、定义接口方法
//com.tanhua.dubbo.server.api.UserLocationApi/*** 查询用户地理位置** @param userId* @return*/UserLocationVo queryByUserId(Long userId);/*** 根据位置搜索** @param longitude 经度* @param latitude 纬度* @param distance 距离(米)* @param page 页数* @param pageSize 页面大小*/PageInfo<UserLocationVo> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize);
2.1.2、编写实现
//com.tanhua.dubbo.es.api.UserLocationApiImpl/*** 查询用户的位置信息** @param userId* @return*/@Overridepublic UserLocationVo queryByUserId(Long userId) {GetQuery getQuery = new GetQuery();getQuery.setId(String.valueOf(userId));UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);if(ObjectUtil.isNotEmpty(userLocation)){return UserLocationVo.format(userLocation);}return null;}/*** 根据位置搜索** @param longitude 经度* @param latitude 纬度* @param distance 距离(米)* @param page 页数* @param pageSize 页面大小*/@Overridepublic PageInfo<UserLocationVo> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {PageInfo<UserLocationVo> pageInfo = new PageInfo<>();pageInfo.setPageNum(page);pageInfo.setPageSize(pageSize);String fieldName = "location";//实现了SearchQuery接口,构造分页、排序NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();//分页PageRequest pageRequest = PageRequest.of(page - 1, pageSize);searchQueryBuilder.withPageable(pageRequest);BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//以一个点为中心,指定范围查询GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);//中心点geoDistanceQueryBuilder.point(new GeoPoint(latitude, longitude));//距离(画圆的半径)单位:公里geoDistanceQueryBuilder.distance(distance / 1000, DistanceUnit.KILOMETERS);boolQueryBuilder.must(geoDistanceQueryBuilder);searchQueryBuilder.withQuery(boolQueryBuilder);//排序,由近到远排序GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder(fieldName, latitude, longitude);geoDistanceSortBuilder.order(SortOrder.ASC); //正序排序geoDistanceSortBuilder.unit(DistanceUnit.KILOMETERS); //设置单位searchQueryBuilder.withSort(geoDistanceSortBuilder);AggregatedPage<UserLocation> aggregatedPage = this.elasticsearchTemplate.queryForPage(searchQueryBuilder.build(), UserLocation.class);if(CollUtil.isEmpty(aggregatedPage.getContent())){return pageInfo;}pageInfo.setRecords(UserLocationVo.formatToList(aggregatedPage.getContent()));return pageInfo;}
2.1.3、单元测试
//com.tanhua.dubbo.es.TestUserLocationApi@Testpublic void testQueryByUserId(){UserLocationVo userLocationVo = this.userLocationApi.queryByUserId(1L);System.out.println(userLocationVo);}@Testpublic void testQueryUserFromLocation(){UserLocationVo userLocationVo = this.userLocationApi.queryByUserId(1L);PageInfo<UserLocationVo> pageInfo = this.userLocationApi.queryUserFromLocation(userLocationVo.getLongitude(),userLocationVo.getLatitude(), 5000d, 1, 10);pageInfo.getRecords().forEach(vo -> System.out.println(vo));}
2.2、APP接口服务
文档地址:https://mock-java.itheima.net/project/35/interface/api/611
2.2.1、NearUserVo
package com.tanhua.server.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class NearUserVo {private Long userId;private String avatar;private String nickname;}
2.2.2、TanHuaController
//com.tanhua.server.controller.TanHuaController/*** 搜附近** @param gender* @param distance* @return*/@GetMapping("search")public ResponseEntity<List<NearUserVo>> queryNearUser(@RequestParam(value = "gender", required = false) String gender,@RequestParam(value = "distance", defaultValue = "2000") String distance) {try {List<NearUserVo> list = this.tanHuaService.queryNearUser(gender, distance);return ResponseEntity.ok(list);} catch (Exception e) {e.printStackTrace();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}
2.2.3、TanHuaService
//com.tanhua.server.service.TanHuaServicepublic List<NearUserVo> queryNearUser(String gender, String distance) {//查询当前用户的位置User user = UserThreadLocal.get();UserLocationVo userLocationVo = this.userLocationApi.queryByUserId(user.getId());if(ObjectUtil.isEmpty(userLocationVo)){return ListUtil.empty();}PageInfo<UserLocationVo> pageInfo = this.userLocationApi.queryUserFromLocation(userLocationVo.getLongitude(),userLocationVo.getLatitude(),Convert.toDouble(distance),1,50);List<UserLocationVo> records = pageInfo.getRecords();if(CollUtil.isEmpty(records)){return ListUtil.empty();}//构造筛选条件List<Object> userIdList = CollUtil.getFieldValues(records, "userId");QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.in("user_id", userIdList);if(StrUtil.equalsIgnoreCase(gender, "man")){queryWrapper.eq("sex", SexEnum.MAN);}else if(StrUtil.equalsIgnoreCase(gender, "woman")){queryWrapper.eq("sex", SexEnum.WOMAN);}List<UserInfo> userInfoList = this.userInfoService.queryUserInfoList(queryWrapper);List<NearUserVo> result = new ArrayList<>();for (UserLocationVo locationVo : records) {//排除自己if(ObjectUtil.equals(locationVo.getUserId(), user.getId())){continue;}for (UserInfo userInfo : userInfoList) {if(ObjectUtil.equals(locationVo.getUserId(), userInfo.getUserId())){NearUserVo nearUserVo = new NearUserVo();nearUserVo.setUserId(userInfo.getUserId());nearUserVo.setAvatar(userInfo.getLogo());nearUserVo.setNickname(userInfo.getNickName());result.add(nearUserVo);break;}}}return result;}
2.2.4、测试
3、探花
探花功能是将推荐的好友随机的通过卡片的形式展现出来,用户可以选择左滑、右滑操作,左滑:“不喜欢”,右滑:“喜欢”。
喜欢:如果双方喜欢,那么就会成为好友。
如果已经喜欢或不喜欢的用户在列表中不再显示。
3.1、喜欢的dubbo服务
用户的喜欢与不喜欢列表需要保存在redis中,为了防止redis中的数据丢失,同时需要将数据保存到mongodb进行持久化保存。
3.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 Long created; //创建时间}
3.1.2、定义接口
package com.tanhua.dubbo.server.api;import java.util.List;public interface UserLikeApi {/*** 喜欢** @param userId* @param likeUserId* @return*/Boolean likeUser(Long userId, Long likeUserId);/*** 不喜欢** @param userId* @param likeUserId* @return*/Boolean notLikeUser(Long userId, Long likeUserId);/*** 是否相互喜欢** @param userId* @param likeUserId* @return*/Boolean isMutualLike(Long userId, Long likeUserId);/*** 查询喜欢列表** @param userId* @return*/List<Long> queryLikeList(Long userId);/*** 查询不喜欢列表** @param userId* @return*/List<Long> queryNotLikeList(Long userId);}
3.1.3、编写实现
package com.tanhua.dubbo.server.api;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.pojo.UserLike;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;import java.util.ArrayList;
import java.util.List;
import java.util.Set;@Service(version = "1.0.0")
public class UserLikeApiImpl implements UserLikeApi {@Autowiredprivate MongoTemplate mongoTemplate;@Autowiredprivate RedisTemplate<String,String> redisTemplate;public static final String LIKE_REDIS_KEY_PREFIX = "USER_LIKE_";public static final String NOT_LIKE_REDIS_KEY_PREFIX = "USER_NOT_LIKE_";/*** 喜欢** @param userId* @param likeUserId* @return*/@Overridepublic Boolean likeUser(Long userId, Long likeUserId) {//判断该用户是否已经喜欢,如果已经喜欢就返回if(this.isLike(userId, likeUserId)){return false;}UserLike userLike = new UserLike();userLike.setId(ObjectId.get());userLike.setUserId(userId);userLike.setLikeUserId(likeUserId);userLike.setCreated(System.currentTimeMillis());//将数据存储到MongoDBthis.mongoTemplate.save(userLike);//用户的喜欢数据保存到redis//用户1:key -> USER_LIKE_1 , value -> 2, "1"//用户1:key -> USER_LIKE_1 , value -> 3, "1"//用户2:key -> USER_LIKE_2 , value -> 4, "1"String redisKey = this.getLikeRedisKey(userId);String hashKey = String.valueOf(likeUserId);this.redisTemplate.opsForHash().put(redisKey, hashKey, "1");//判断,喜欢的用户是否在不喜欢的列表中,如果在,就需要删除数据if(this.isNotLike(userId, likeUserId)){redisKey = this.getNotLikeRedisKey(userId);this.redisTemplate.opsForHash().delete(redisKey, hashKey);}return true;}/*** 获取喜欢数据的redis key** @param userId* @return*/private String getLikeRedisKey(Long userId){return LIKE_REDIS_KEY_PREFIX + userId;}/*** 获取不喜欢数据的redis key** @param userId* @return*/private String getNotLikeRedisKey(Long userId){return NOT_LIKE_REDIS_KEY_PREFIX + userId;}/*** 是否喜欢** @param userId* @param likeUserId* @return*/private Boolean isLike(Long userId, Long likeUserId){String redisKey = this.getLikeRedisKey(userId);String hashKey = String.valueOf(likeUserId);return this.redisTemplate.opsForHash().hasKey(redisKey, hashKey);}/*** 是否不喜欢** @param userId* @param likeUserId* @return*/private Boolean isNotLike(Long userId, Long likeUserId){String redisKey = this.getNotLikeRedisKey(userId);String hashKey = String.valueOf(likeUserId);return this.redisTemplate.opsForHash().hasKey(redisKey, hashKey);}@Overridepublic Boolean notLikeUser(Long userId, Long likeUserId) {//判断用户是否已经不喜欢,如果已经不喜欢,就返回if(this.isNotLike(userId, likeUserId)){return false;}//将用户保存到不喜欢列表中String redisKey = this.getNotLikeRedisKey(userId);String hashKey = String.valueOf(likeUserId);this.redisTemplate.opsForHash().put(redisKey, hashKey, "1");//判断用户是否在喜欢列表中,如果存在的话,需要删除数据if(this.isLike(userId, likeUserId)){//删除MongoDB数据Query query = Query.query(Criteria.where("userId").is(userId).and("likeUserId").is(likeUserId));this.mongoTemplate.remove(query, UserLike.class);//删除redis中的数据redisKey = this.getLikeRedisKey(userId);this.redisTemplate.opsForHash().delete(redisKey, hashKey);}return true;}@Overridepublic Boolean isMutualLike(Long userId, Long likeUserId) {return this.isLike(userId, likeUserId)&& this.isLike(likeUserId, userId);}@Overridepublic List<Long> queryLikeList(Long userId) {// 查询redisString redisKey = this.getLikeRedisKey(userId);Set<Object> keys = this.redisTemplate.opsForHash().keys(redisKey);if(CollUtil.isEmpty(keys)){return ListUtil.empty();}List<Long> result = new ArrayList<>(keys.size());keys.forEach(o -> result.add(Convert.toLong(o)));return result;}@Overridepublic List<Long> queryNotLikeList(Long userId) {// 查询redisString redisKey = this.getNotLikeRedisKey(userId);Set<Object> keys = this.redisTemplate.opsForHash().keys(redisKey);if(CollUtil.isEmpty(keys)){return ListUtil.empty();}List<Long> result = new ArrayList<>(keys.size());keys.forEach(o -> result.add(Convert.toLong(o)));return result;}
}
3.1.4、单元测试
package com.tanhua.dubbo.server.api;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
public class TestUserLikeApi {@Autowiredprivate UserLikeApi userLikeApi;@Testpublic void testUserLike() {System.out.println(this.userLikeApi.likeUser(1L, 2L));System.out.println(this.userLikeApi.likeUser(1L, 3L));System.out.println(this.userLikeApi.likeUser(1L, 4L));System.out.println(this.userLikeApi.notLikeUser(1L, 5L));System.out.println(this.userLikeApi.notLikeUser(1L, 6L));System.out.println(this.userLikeApi.likeUser(1L, 5L));System.out.println(this.userLikeApi.notLikeUser(1L, 2L));}@Testpublic void testQueryList(){this.userLikeApi.queryLikeList(1L).forEach(a -> System.out.println(a));System.out.println("-------");this.userLikeApi.queryNotLikeList(1L).forEach(a -> System.out.println(a));}
}
3.2、查询推荐列表dubbo服务
3.2.1、定义接口
//com.tanhua.dubbo.server.api.RecommendUserApi/*** 查询探花列表,查询时需要排除不喜欢列表用户** @param userId* @param count* @return*/
List<RecommendUser> queryCardList(Long userId, Integer count);
3.2.2、编写实现
//com.tanhua.dubbo.server.api.RecommendUserApiImpl@Overridepublic List<RecommendUser> queryCardList(Long userId, Integer count) {//设置分页以及排序,按照得分倒序排序PageRequest pageRequest = PageRequest.of(0, count, Sort.by(Sort.Order.desc("score")));//排除已喜欢或不喜欢的用户List<Long> userIds = new ArrayList<>();//查询喜欢列表userIds.addAll(this.userLikeApi.queryLikeList(userId));//查询不喜欢列表userIds.addAll(this.userLikeApi.queryNotLikeList(userId));//构造查询条件Criteria criteria = Criteria.where("toUserId").is(userId);if(CollUtil.isNotEmpty(userIds)){//加入到查询条件中,排除这些用户criteria.andOperator(Criteria.where("userId").nin(userIds));}Query query = Query.query(criteria).with(pageRequest);List<RecommendUser> recommendUserList = this.mongoTemplate.find(query, RecommendUser.class);return recommendUserList;}
3.2.3、单元测试
//com.tanhua.dubbo.server.api.TestRecommendUserApi@Test
public void testQueryCardList(){this.recommendUserApi.queryCardList(2L, 20).forEach(recommendUser -> System.out.println(recommendUser));
}
3.3、查询推荐列表APP接口实现
接口文档:https://mock-java.itheima.net/project/35/interface/api/593
3.3.1、TanHuaController
/*** 探花** @return*/@GetMapping("cards")public ResponseEntity<List<TodayBest>> queryCardsList() {try {List<TodayBest> list = this.tanHuaService.queryCardsList();return ResponseEntity.ok(list);} catch (Exception e) {e.printStackTrace();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}
3.3.2、TanHuaService
#默认推荐列表
tanhua.default.recommend.users=2,3,8,10,18,20,24,29,27,32,36,37,56,64,75,88
/*** 查询推荐卡片列表,从推荐列表中随机选取10个用户** @return*/public List<TodayBest> queryCardsList() {User user = UserThreadLocal.get();int count = 50;//查询到的50条数据,并不是用来直接展现,需要从这50条数据中随机返回一些数据List<RecommendUser> recommendUserList = this.recommendUserService.queryCardList(user.getId(), count);if (CollUtil.isEmpty(recommendUserList)) {recommendUserList = new ArrayList<>();//默认推荐列表List<String> list = StrUtil.split(defaultRecommendUsers, ',');for (String userId : list) {RecommendUser recommendUser = new RecommendUser();recommendUser.setToUserId(user.getId());recommendUser.setUserId(Convert.toLong(userId));recommendUserList.add(recommendUser);}}//计算展现的数量,默认展现10个int showCount = Math.min(10, recommendUserList.size());List<RecommendUser> result = new ArrayList<>();for (int i = 0; i < showCount; i++) {//TODO 可能重复int index = RandomUtil.randomInt(0, recommendUserList.size());RecommendUser recommendUser = recommendUserList.get(index);result.add(recommendUser);}List<Object> userIdList = CollUtil.getFieldValues(result, "userId");List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIdList);List<TodayBest> todayBests = new ArrayList<>();for (UserInfo userInfo : userInfoList) {TodayBest todayBest = new TodayBest();todayBest.setId(userInfo.getUserId());todayBest.setAge(userInfo.getAge());todayBest.setAvatar(userInfo.getLogo());todayBest.setGender(userInfo.getSex().name().toLowerCase());todayBest.setNickname(userInfo.getNickName());todayBest.setTags(Convert.toStrArray(StrUtil.split(userInfo.getTags(), ',')));todayBest.setFateValue(0L);todayBests.add(todayBest);}return todayBests;}
3.3.3、测试
效果:
3.4、左滑右滑
左滑:“不喜欢”,右滑:“喜欢”,如果双方喜欢,那么就会成为好友。
喜欢的接口文档:https://mock-java.itheima.net/project/35/interface/api/599
不喜欢的接口文档:https://mock-java.itheima.net/project/35/interface/api/605
3.4.1、TanHuaController
/*** 喜欢** @param likeUserId* @return*/
@GetMapping("{id}/love")
public ResponseEntity<Void> likeUser(@PathVariable("id") Long likeUserId) {try {this.tanHuaService.likeUser(likeUserId);return ResponseEntity.ok(null);} catch (Exception e) {e.printStackTrace();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}/*** 不喜欢** @param likeUserId* @return*/
@GetMapping("{id}/unlove")
public ResponseEntity<Void> notLikeUser(@PathVariable("id") Long likeUserId) {try {this.tanHuaService.notLikeUser(likeUserId);return ResponseEntity.ok(null);} catch (Exception e) {e.printStackTrace();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
3.4.2、TanHuaService
//com.tanhua.server.service.TanHuaServicepublic Boolean likeUser(Long likeUserId) {User user = UserThreadLocal.get();Boolean result = this.userLikeApi.likeUser(user.getId(), likeUserId);if (!result) {return false;}if (this.userLikeApi.isMutualLike(user.getId(), likeUserId)) {//相互喜欢成为好友this.imService.contactUser(likeUserId);}return true;
}public Boolean notLikeUser(Long likeUserId) {User user = UserThreadLocal.get();return this.userLikeApi.notLikeUser(user.getId(), likeUserId);
}
3.4.3、测试
user_like表,可以看到已经相互喜欢了:
tanhua_users表,可以看到相互是好友了:
环信平台:
4、用户资料
在我的中心模块中,可以对个人信息做修改。
4.1、基本信息
在前面实现的查询个人信息接口中,已经返回个人基本数据,所以可以直接展现出个人信息,下面只需要进行实现数据的保存即可。
4.4.1、接口信息
接口地址:https://mock-java.itheima.net/project/35/interface/api/887
请求参数:
4.4.2、MyCenterController
//com.tanhua.server.controller.MyCenterController/*** 更新用户信息** @param userInfoVo* @return*/
@PutMapping
public ResponseEntity<Void> updateUserInfo(@RequestBody UserInfoVo userInfoVo){try {Boolean bool = this.myCenterService.updateUserInfo(userInfoVo);if(bool){return ResponseEntity.ok(null);}} catch (Exception e) {e.printStackTrace();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
4.4.3、MyCenterService
//com.tanhua.server.service.MyCenterServicepublic Boolean updateUserInfo(UserInfoVo userInfoVo) {User user = UserThreadLocal.get();UserInfo userInfo = new UserInfo();userInfo.setUserId(user.getId());userInfo.setAge(Integer.valueOf(userInfoVo.getAge()));userInfo.setSex(StringUtils.equalsIgnoreCase(userInfoVo.getGender(), "man") ? SexEnum.MAN : SexEnum.WOMAN);userInfo.setBirthday(userInfoVo.getBirthday());userInfo.setCity(userInfoVo.getCity());userInfo.setEdu(userInfoVo.getEducation());userInfo.setIncome(StringUtils.replaceAll(userInfoVo.getIncome(), "K", ""));userInfo.setIndustry(userInfoVo.getProfession());userInfo.setMarriage(userInfoVo.getMarriage() == 1 ? "已婚" : "未婚");return this.userInfoService.updateUserInfoByUserId(userInfo);
}
4.4.4、UserInfoService
//com.tanhua.server.service.UserInfoServicepublic boolean updateUserInfoByUserId(UserInfo userInfo) {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("user_id", userInfo.getUserId());return this.userInfoMapper.update(userInfo, queryWrapper) > 0;
}
4.4.5、bug修复
在之前的查询个人信息中接口中,返回数据中的性别数据有误,需要返回man或woman。
如下:
//com.tanhua.server.service.MyCenterServicepublic UserInfoVo queryUserInfoByUserId(Long userId) {if (ObjectUtil.isEmpty(userId)) {//如果查询id为null,就表示查询当前用户信息userId = UserThreadLocal.get().getId();}//查询用户信息UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(userId);if (ObjectUtil.isEmpty(userInfo)) {return null;}UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class, "marriage");userInfoVo.setGender(userInfo.getSex().getValue() == 1 ? "man" : "women");userInfoVo.setMarriage(StrUtil.equals("已婚", userInfo.getMarriage()) ? 1 : 0);return userInfoVo;}
4.2、更新头像
上传头像使用sso中的上传逻辑即可,只是路径不同,所以我们只需要修改nginx配置和在sso中定义Controller即可。
接口文档:https://mock-java.itheima.net/project/35/interface/api/881
#user nobody;
worker_processes 1;#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections 1024;
}http {include mime.types;default_type application/octet-stream;#log_format main '$remote_addr - $remote_user [$time_local] "$request" '# '$status $body_bytes_sent "$http_referer" '# '"$http_user_agent" "$http_x_forwarded_for"';#access_log logs/access.log main;sendfile on;#tcp_nopush on;#keepalive_timeout 0;keepalive_timeout 65;#gzip on;server {listen 80;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;#error_page 404 /404.html;# redirect server error pages to the static page /50x.html#error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}location /user/ { #请求路径中凡是以/user/开头的请求,转发到sso系统client_max_body_size 300m; #设置最大的请求体大小,解决大文件上传不了的问题proxy_connect_timeout 300s; #代理连接超时时间proxy_send_timeout 300s; #代理发送数据的超时时间proxy_read_timeout 300s; #代理读取数据的超时时间proxy_pass http://127.0.0.1:18080; #转发请求}location /users/header { #请求路径中凡是以/user/header开头的请求,转发到sso系统client_max_body_size 300m; #设置最大的请求体大小,解决大文件上传不了的问题proxy_connect_timeout 300s; #代理连接超时时间proxy_send_timeout 300s; #代理发送数据的超时时间proxy_read_timeout 300s; #代理读取数据的超时时间proxy_pass http://127.0.0.1:18080; #转发请求}location / { #上面未匹配到的在这里处理client_max_body_size 300m;proxy_connect_timeout 300s;proxy_send_timeout 300s;proxy_read_timeout 300s;proxy_pass http://127.0.0.1:18081; #转发请求到server系统}}}
4.2.2、MyCenterController
在sso工程中定义MyCenterController。
package com.tanhua.sso.controller;import com.tanhua.sso.vo.ErrorResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("users")
public class MyCenterController {@Autowiredprivate UserInfoController userInfoController;/*** 上传头像** @param file* @param token* @return*/@PostMapping("header")public ResponseEntity<Object> saveLogo(@RequestParam("headPhoto") MultipartFile file, @RequestHeader("Authorization") String token) {return this.userInfoController.saveUserLogo(file, token);}
}
探花交友_第8章_搜附近以及探花功能实现相关推荐
- 探花交友_第1章_项目介绍以及实现登录功能_第1节_功能介绍
探花交友_第1章_项目介绍以及实现登录功能_第1节_功能介绍 文章目录 探花交友_第1章_项目介绍以及实现登录功能_第1节_功能介绍 1.功能介绍 1.1.功能列表 1.2.注册登录 1.3.交友 1 ...
- 探花交友_第3章_今日佳人功能实现
探花交友_第3章_今日佳人功能实现 文章目录 探花交友_第3章_今日佳人功能实现 1.首页 2.系统架构 2.1.nginx服务 2.1.1.部署安装 2.1.2.配置 2.1.3.测试 2.2.搭建 ...
- 探花交友_第10章_实现推荐功能
探花交友_第10章_实现推荐功能 文章目录 探花交友_第10章_实现推荐功能 1.了解推荐系统 1.1.什么是推荐系统? 1.2.电商是推荐系统的先行者 1.3.推荐系统业务流程 1.4.协同过滤推荐 ...
- 探花交友_第2章_环境搭建(新版)
探花交友_第2章_环境搭建(新版) 文章目录 探花交友_第2章_环境搭建(新版) 课程介绍 <探花交友> 1.项目介绍 1.1.项目背景 1.2.市场分析 1.3.目标用户群体 1.4.使 ...
- 探花交友_第10章_搭建后台系统(新版)
探花交友_第10章_搭建后台系统(新版) 文章目录 探花交友_第10章_搭建后台系统(新版) 1.1 概述 1.2 API网关 1.2.1 搭建网关 依赖 引导类 跨域问题配置类 配置文件 测试 1. ...
- 探花交友_第9章_小视频方案(新版)
探花交友_第9章_小视频方案(新版) 文章目录 探花交友_第9章_小视频方案(新版) 1. 我的访客 1.1 需求分析 1.1.1 功能说明 1.1.2 数据库表 1.2 记录访客数据 tanhua- ...
- 探花交友_第12章_实现推荐系统(新版)
探花交友_第12章_实现推荐系统(新版) 文章目录 探花交友_第12章_实现推荐系统(新版) 1.了解推荐系统 1.1.什么是推荐系统? 1.2.电商是推荐系统的先行者 1.3.推荐系统业务流程 1. ...
- 探花交友_第11章_数据统计与内容审核(新版)
探花交友_第11章_数据统计与内容审核(新版) 文章目录 探花交友_第11章_数据统计与内容审核(新版) 1.用户冻结解冻 1.1 用户冻结 ManageController ManageServic ...
- 探花交友_第6章_圈子互动(新版)
探花交友_第6章_圈子互动(新版) 文章目录 探花交友_第6章_圈子互动(新版) 课程说明 1. 动态查询 1.1 查询好友动态 1.1.1 接口文档 1.1.2 代码步骤 1.1.3 代码实现 ta ...
最新文章
- UE商城资源 Kitsune狐狸女孩
- 026_html表单
- php 单利模式实例化,php-单例模式实现mysql实例化对象
- python画图程序没有图_python画图 - v0
- Vue「六」前端路由、vue-router
- jsp springmvc 视图解析器_SpringMVC工作原理
- android 布局颜色设置颜色设置,怎么在Android中利用view设置布局颜色
- 自定义控件的构建(10)
- bzoj 1620: [Usaco2008 Nov]Time Management 时间管理(贪心)
- Disruptor(无锁并发框架)-发布
- flowable工作流_使用Bash Shell实现flowable配置文件修改定制
- Python numpy 中 keepdims 的含义
- Varnish的基本应用详解
- 电脑c盘空间不足怎么清理_C盘空间不足怎么办?如何给C盘扩容?
- 基于C++实现DBSCAN聚类算法
- 转载:如何使用RFT自动打开IE
- z-index诡异事件之背锅侠
- 第十一届蓝桥杯国赛题目
- html向服务器发送请求有哪些方法,HTTP协议客户端是如何向服务器发送请求
- 玩转 Defcon 黑客大会,这里有份装 X 指南
热门文章
- 数学日记-最小二乘法本质
- JavaScript调用麦克风并录制wav音频
- 禁用免费版小红伞的弹窗广告
- 腾讯后台开发技术总监浅谈过载保护 小心雪崩效应
- 2月14魔兽服务器维护,《魔兽世界怀旧服》2月14日再开两组服务器 免费转服同步上线...
- JavaEE 5 (4/28)
- 题解-bzoj2560 串珠子
- [案例研究]—superJumper 3.游戏中的物体与主游戏逻辑
- 【多式联运】基于matlab遗传算法求解多式联运运输问题(考虑碳交易)【含Matlab源码 1997期】
- 文艺小清新 古镇留踪