# Java 项目实战: 交友项目 - 左滑不喜欢:无配对检查的推荐排除

发布时间:2026/11/2 6:38:42
# Java 项目实战: 交友项目 - 左滑不喜欢:无配对检查的推荐排除

概述

左滑「不喜欢」和右滑「喜欢」代码结构几乎一样,但产品语义完全不同:右滑可能触发配对,左滑只负责把人从推荐里排出去。本文讲清这个不对称性带来的实现差异,以及「不感兴趣」对推荐列表的持久影响。

纲要

  • 左滑与右滑的产品不对称性:为什么左滑不做配对检查
  • 接口契约:api/109(GET /mf/{id}/unlove)
  • 实现总览:notLikeUser与likeUser的镜像对照(链路细节见「左滑右滑实现」相关文章)
  • 核心增量:「不感兴趣」的推荐影响,queryCardsList的NIN排除与反悔机制
  • 时序图、踩坑清单、验证步骤## 左滑与右滑的产品不对称

项目推荐页里,右滑是强动作:我喜欢的人,对方也喜欢我,系统要立刻建好友关系(项目好友表 + 环信)。左滑是弱动作:它不是「拒绝这个人」,而是给推荐系统一条反馈信号,「这个方向不对,别再推了」。

这个语义差异直接决定了代码差异:

维度右滑 likeUser左滑 notLikeUser
Mongo 存储saveOrUpdate(userId, likeUserId, true)saveOrUpdate(userId, likeUserId, false)
Redis 操作SADD LIKE,SREM NOT_LIKESADD NOT_LIKE,SREM LIKE
配对检查有,双向喜欢则加好友没有
计数项目页有喜欢计数无计数

左滑为什么不做配对检查?倒推一下就明白:推荐列表(queryCardsList)在查询时已经排除了所有喜欢过和不喜欢的用户,对方根本不会出现在你的左滑列表里;同时你也不在对方的排除名单之外,换句话说,能被你左滑的人,必然是「你还没表过态」的人,不存在「你们已经是好友再左滑」的正常路径。所以「双向喜欢则取消好友关系」这一步在当前系统里走不到,本文将其留作自行实现的练习(真要做,需同时删项目好友表和环信好友数据)。
有个细节别漏:notLikeUser里 SREM LIKE 这一行不能省。用户可能先右滑了某人,后来反悔左滑,这时 Mongo 里的态度会翻转,但 Redis 的 LIKE 集合如果不删,配对判断isLike(基于 Redis)仍会认为双方互相喜欢。Mongo 和 Redis 必须一起翻转,这是和likeUser完全镜像的操作。

接口契约(api/109)

YAPI 接口编号 109,请求路径与右滑的love相同,仅动作后缀不同:

项目内容
接口路径GET /mf/{id}/unlove
请求方式GET
路径参数id,被左滑的用户 ID
请求体无
响应ResponseEntity<Void>,200 即成功
// 请求示例GET/mf/3/unlove// 响应:空 body,状态码 200

控制器实现只做转发,实读mfController:

/** * 不喜欢 */@GetMapping("{id}/unlove")publicResponseEntity<Void>notLikeUser(@PathVariable("id")LonglikeUserId){this.mfService.notLikeUser(likeUserId);returnResponseEntity.ok(null);}

实现总览:与 likeUser 镜像

mfService.notLikeUser的链路结构和likeUser一模一样,只有两处参数相反(完整逐行讲解见「左滑右滑实现」相关文章,这里给总览):

// 不喜欢 —— 结构与 likeUser 镜像publicvoidnotLikeUser(LonglikeUserId){//1、调用API,保存不喜欢数据到MongoDB(isLike = false)Booleansave=userLikeApi.saveOrUpdate(UserHolder.getUserId(),likeUserId,false);if(!save){thrownewBusinessException(ErrorResult.error());}//2、操作redis:写入不喜欢集合,删除喜欢集合(与 likeUser 恰好相反)redisTemplate.opsForSet().add(Constants.USER_NOT_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString());redisTemplate.opsForSet().remove(Constants.USER_LIKE_KEY+UserHolder.getUserId(),likeUserId.toString());//3、判断双向喜欢则删除好友 —— 当前推荐逻辑下走不到,留作练习}

Redis key 常量在Constants中,值实读为"USER_NOT_LIKE_SET_",按用户 ID 拼接成每人一个 Set:

//用户不喜欢Redis的keypublicstaticfinalStringUSER_NOT_LIKE_KEY="USER_NOT_LIKE_SET_";

「不感兴趣」的推荐影响:NIN 排除

这是左滑真正的产品价值所在。推荐卡片列表的查询在RecommendUserApiImpl.queryCardsList中,先查出当前用户在user_like表里的全部表态对象,再用nin从推荐池里剔除:

publicList<RecommendUser>queryCardsList(LonguserId,intcounts){//1、查询喜欢不喜欢的用户IDList<UserLike>likeList=mongoTemplate.find(Query.query(Criteria.where("userId").is(userId)),UserLike.class);List<Long>likeUserIdS=CollUtil.getFieldValues(likeList,"likeUserId",Long.class);//2、构造查询推荐用户的条件Criteriacriteria=Criteria.where("toUserId").is(userId).and("userId").nin(likeUserIdS);//3、随机获取推荐的用户列表TypedAggregation<RecommendUser>newAggregation=TypedAggregation.newAggregation(RecommendUser.class,Aggregation.match(criteria),Aggregation.sample(counts));AggregationResults<RecommendUser>results=mongoTemplate.aggregate(newAggregation,RecommendUser.class);returnresults.getMappedResults();}

注意这里查user_like时没有过滤isLike,不管你右滑过还是左滑过,都进排除名单。所以左滑的效果是持久的:被你左滑的人,从此永远不再出现在你的推荐列表里,直到你改变态度。这就是「不感兴趣」的产品语义:一次左滑 = 长期排除,而不是本轮跳过。

反悔怎么办?答案是再滑一次。UserLikeApiImpl.saveOrUpdate按(userId, likeUserId)查记录,存在就只更新isLike字段,同一条记录承载两种态度,左滑变右滑时记录被原地翻转为isLike=true,NIN名单随表态变化自动更新,这个人就重新回到推荐池。这个设计在「数据建模」相关文章中(user_like一条记录两种态度)已经铺垫过,左滑场景正是它发挥价值的地方:不需要删记录再重建,态度翻转零成本。具体的「左滑后想右滑」反悔流程,「左滑右滑实现」相关文章已有完整讲解,此处不重复。
反悔怎么办?答案是再滑一次。UserLikeApiImpl.saveOrUpdate按(userId, likeUserId)查记录,存在就只更新isLike字段,同一条记录承载两种态度,左滑变右滑时记录被原地翻转为isLike=true,NIN名单随表态变化自动更新,这个人就重新回到推荐池。这个设计在数据建模篇(user_like一条记录两种态度)已经铺垫过,左滑场景正是它发挥价值的地方:不需要删记录再重建,态度翻转零成本。具体的「左滑后想右滑」反悔流程,左滑右滑实现篇已有完整讲解,此处不重复。

时序图

RedisMongoDBUserLikeApi(Dubbo)mfServicemfController客户端RedisMongoDBUserLikeApi(Dubbo)mfServicemfController客户端无配对检查(推荐列表已排除已表态用户)GET /mf/{id}/unlovenotLikeUser(likeUserId)saveOrUpdate(userId, likeUserId, false)存在则更新 isLike=false,不存在则插入trueSADD USER_NOT_LIKE_SET_{userId} likeUserIdSREM USER_LIKE_SET_{userId} likeUserId返回200 OK

常见误解澄清

误解实际情况
左滑也该判断双向喜欢,取消好友推荐查询已排除表态过的用户,正常流程左滑不到好友;该步骤留作练习
左滑只是「这轮跳过」,下次还会推荐NIN按user_like全量排除,左滑是长期排除
反悔左滑要删数据重新右滑翻转同一条记录的isLike即可,记录不删
排除名单存在 Redis 里queryCardsList读的是 Mongo 的user_like,Redis Set 只服务于配对判断
左滑接口路径是/mf/{id}/love机翻稿此处转写有误,实读代码与 api/109 均为{id}/unlove

踩坑清单

  • 照抄 likeUser 带上配对检查:最常见的手滑。左滑调用messagesService.contacts会导致「我讨厌的人成了我好友」级别的逻辑事故。
  • 忘了 SREM LIKE:只 SADD NOT_LIKE 不删 LIKE,先右滑后左滑的用户在 Redis 里两边都有,isLike配对判断失真。
  • Mongo 与 Redis 态度不一致:两步操作无事务保护,失败路径要抛BusinessException让前端感知,别吞异常。
  • 产品层面没有反悔入口:NIN排除是永久性的,客户端如果不提供「重新推荐」或历史列表入口,用户误滑后无解,这是产品设计要提前想清楚的点。

验证

按课程实测路径走一遍(106 号用户):

  1. 登录 106 号用户,进入项目推荐页,连续左滑若干人。
  2. 查 Mongouser_like集合:出现userId=106, isLike=false的记录。
  3. 查 Redis:USER_NOT_LIKE_SET_106中有被左滑的用户 ID,SMEMBERS可确认。
  4. 退出重进项目页:推荐列表人数变少,被左滑的人不再出现。
  5. (反悔验证)对该用户再右滑:user_like记录isLike翻转为true,推荐列表恢复出现此人。

总结

  • 左滑是推荐反馈信号,不是社交拒绝动作;右滑强(可能配对建好友),左滑弱(只影响推荐),这个不对称性解释了为什么notLikeUser不做配对检查。
  • 实现上与likeUser完全镜像:MongosaveOrUpdate(isLike=false)+ Redis SADD NOT_LIKE / SREM LIKE,链路细节见左滑右滑实现篇。
  • 左滑的核心增量在推荐侧:queryCardsList用NIN排除user_like全部表态记录,左滑 = 永久排除;反悔靠态度翻转更新isLike,一条记录两种态度的设计在此兑现价值。
  • 至此项目模块的喜欢/不喜欢/配对链路全部走通,本章收尾内容见同目录总结和作业。