课程说明
- 上报地位位置
- 实现搜附近功能
- 实现探花功能
- 用户基本信息维护
1、上报地理位置
当客户端检测用户的地理位置,当变化大于500米时或每隔5分钟,向服务端上报地理位置。
用户的地理位置存储到Elasticsearch中,需要使用环境提供的ES集群,如下:
1.1、dubbo服务
用户地理位置的服务独立一个新的工程来实现,名字为:my-tanhua-dubbo-es。1.1.1、创建工程
pom.ml文件如下:
application.properties文件:<?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>
启动类:# Spring boot applicationspring.application.name = itcast-tanhua-dubbo-es# dubbo 扫描包配置dubbo.scan.basePackages = com.tanhua.dubbo.esdubbo.application.name = dubbo-provider-es#dubbo 对外暴露的端口信息dubbo.protocol.name = dubbodubbo.protocol.port = 20882#dubbo注册中心的配置dubbo.registry.address = zookeeper://192.168.31.81:2181dubbo.registry.client = zkclientdubbo.registry.timeout = 60000#ES集群配置spring.data.elasticsearch.cluster-name=es-tanhua-clusterspring.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@AllArgsConstructorpublic 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")@Slf4jpublic 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) {//查询个人的地理位置数据,如果不存在,需要新增,如果是存在数据,更新数据this.initIndex();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)@SpringBootTestpublic 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@Slf4jpublic 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@AllArgsConstructorpublic 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)@SpringBootTestpublic 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@Testpublic 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
@Reference(version = "1.0.0")private RecommendUserApi recommendUserApi;@Value("${tanhua.default.recommend.users}")private String defaultRecommendUsers;@Reference(version = "1.0.0")private UserLikeApi userLikeApi;/*** 查询推荐卡片列表,从推荐列表中随机选取10个用户** @return*/public List<TodayBest> queryCardsList() {User user = UserThreadLocal.get();int count = 50;//查询到的50条数据,并不是用来直接展现,需要从这50条数据中随机返回一些数据List<RecommendUser> recommendUserList = this.recommendUserApi.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*/@PutMappingpublic 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);}}




