好友功能是目前社交场景的必备功能之一,一般好友相关的功能包含有:关注/取关、我(他)的关注、我(他)的粉丝、共同关注等这样一些功能。
1.关注和取关
1.设计思路
总体思路我们采用MySQL + Redis的方式结合完成。MySQL主要是保存落地数据,而利用Redis的Sets进行集合操作。Sets拥有去重(我们不能多次关注同一用户)功能**。一个用户我们存贮两个集合:一个是保存用户关注的人 另一个是保存关注用户的人.**
1.1 用到的命令
- SADD 添加成员;命令格式: SADD key member [member …] ----- 关注
- SREM 移除某个成员;命令格式: SREM key member [member …] -------取关
- SCARD 统计集合内的成员数;命令格式: SCARD key -------关注/粉丝个数
- SISMEMBER 判断是否是集合成员;命令格式:SISMEMBER key member ---------判断是否关注(如果关注那么只可以点击取关)
- SMEMBERS 查询集合内的成员;命令格式: SMEMBERS key -------列表使用(关注列表和粉丝列表)
- SINTER 查询集合的交集;命令格式: SINTER key [key …] --------共同关注、我关注的人关注了他
2.关注表结构
CREATE TABLE `t_follow` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`diner_id` int(11) NULL DEFAULT NULL COMMENT '用户id' ,
`follow_diner_id` int(11) NULL DEFAULT NULL COMMENT '用户关注的人id' ,
`is_valid` tinyint(1) NULL DEFAULT NULL COMMENT '是否关注,0未关注/取消关注,1已关注',
`create_date` datetime NULL DEFAULT NULL ,
`update_date` datetime NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=6
ROW_FORMAT=COMPACT;
为什么t_follow要使用is_valid标识符?
答:当用户A第一次关注用户B时,会往t_follow表添加一条数据,此时 is_valid值为1,当A取消对B的关注时,不需要删除刚刚插入的数据,只需要更改is_valid变成0即可。当然也可以不用is_valid字段,当A取消对B的关注时,物理删除对应的这条数据,但公司一般删除不会做物理删除。
3.关注/取关业务逻辑
@Service
public class FollowService {@Value("${service.name.ms-oauth-server}")private String oauthServerName;@Resourceprivate RestTemplate restTemplate;@Resourceprivate FollowMapper followMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 关注/取关** @param followDinerId 关注的食客ID* @param isFollowed 是否关注 1=关注 0=取消* @param accessToken 登录用户token* @param path 访问地址* @return*/@Transactional(rollbackFor = Exception.class)public ResultInfo follow(Integer followDinerId, int isFollowed,String accessToken, String path) {// 是否选择了关注对象AssertUtil.isTrue(followDinerId == null || followDinerId < 1, "请选择要关注的人");// 获取登录用户信息SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);// 去关注表中查询看有没有对应数据Follow follow = followMapper.selectFollow(dinerInfo.getId(), followDinerId);// 如果没有关注信息,且要进行关注操作if (follow == null && isFollowed == 1) {// 添加关注信息int count = followMapper.save(dinerInfo.getId(), followDinerId);// 添加关注列表到 Redisif (count == 1) {addToRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE,"关注成功", path, "关注成功");}// isValid值为0,那么是取关,如果isValid=1那么就是关注中// 如果有关注信息,且目前处于取关状态,且要进行关注操作if (follow != null && follow.getIsValid() == 0 && isFollowed == 1) {// 重新关注int count = followMapper.update(follow.getId(), isFollowed);// 添加关注列表if (count == 1) {addToRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE,"关注成功", path, "关注成功");}// 如果有关注信息,且目前处于关注中状态,且要进行取关操作if (follow != null && follow.getIsValid() == 1 && isFollowed == 0) {// 取关int count = followMapper.update(follow.getId(), isFollowed);// 移除 Redis 关注列表removeFromRedisSet(dinerInfo.getId(), followDinerId);return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE,"成功取关", path, "成功取关");}return ResultInfoUtil.buildSuccess(path, "操作成功");}/*** 添加关注列表到 Redis** @param dinerId* @param followDinerId*/
private void addToRedisSet(Integer dinerId, Integer followDinerId) {redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() + dinerId, followDinerId);redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);
}/*** 移除 Redis 关注列表** @param dinerId* @param followDinerId*/
private void removeFromRedisSet(Integer dinerId, Integer followDinerId) {redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() + dinerId, followDinerId);redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);
}}
2.共同关注列表
- 从Redis中读取登录用户的关注列表与查看用户的关注列表,然后进行交集操作,获取共同关注的用户id
- 然后通过用户id数据获取用户基本信息
1.查询共同好友代码
/*** 共同好友** @param dinerId* @param accessToken* @param path* @return*/
public ResultInfo findCommonsFriends(Integer dinerId, String accessToken, String path) {// 是否选择了关注对象AssertUtil.isTrue(dinerId == null || dinerId < 1, "请选择要查看的人");// 获取登录用户信息SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);// 登录用户的关注信息String loginDinerKey = RedisKeyConstant.following.getKey() + dinerInfo.getId();// 登录用户关注的对象的关注信息String dinerKey = RedisKeyConstant.following.getKey() + dinerId;// 计算交集Set<Integer> followingDinerIds = redisTemplate.opsForSet().intersect(loginDinerKey, dinerKey);// 没有if (followingDinerIds == null || followingDinerIds.isEmpty()) {return ResultInfoUtil.buildSuccess(path, new ArrayList<ShortDinerInfo>());}// 根据 ids 查询食客信息ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token=${accessToken}&ids={ids}",ResultInfo.class, accessToken, StrUtil.join(",", followingDinerIds));if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {resultInfo.setPath(path);return resultInfo;}// 处理结果集List<LinkedHashMap> dinerInfoMaps = (ArrayList) resultInfo.getData();List<ShortDinerInfo> dinerInfos = dinerInfoMaps.stream().map(diner -> fillBeanWithMap(diner, new ShortDinerInfo(), true)).collect(Collectors.toList());return ResultInfoUtil.buildSuccess(path, dinerInfos);
}