基于Spring Boot+Java的动漫社交网站添加好友功能实现咨询
好友功能两种实现方案分析与通用思路
方案一:在User类中维护好友列表
- 可行性:这种方案在小型训练营项目/快速原型里完全能用,代码结构简单,不用额外加模块,适合快速搭出功能。但有几个坑要注意:
- 数据库层面:用JPA的话会自动生成中间关联表(比如
user_friends),但这种方式存不了好友关系的额外信息——比如你们哪天加的好友、你给对方的备注,要是后面想加这些功能,改起来会很麻烦。 - 性能问题:用户好友多了之后,查用户资料的时候会自动把整个好友列表都加载出来,容易拖慢接口,得手动加
@ManyToMany(fetch = FetchType.LAZY)改成懒加载。
- 数据库层面:用JPA的话会自动生成中间关联表(比如
- 核心代码示例:
@Entity public class UserProfile { @Id private UUID userId; private String displayName; private Date birthday; private String aboutMe; // 懒加载好友列表,关联中间表 @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "user_friends", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "friend_id") ) private List<UserProfile> friendsList = new ArrayList<>(); // 添加好友:要维护双向关联 public void addFriend(UserProfile friend) { if (!friendsList.contains(friend)) { friendsList.add(friend); friend.getFriendsList().add(this); } } // 移除好友:同样要删两边 public void removeFriend(UserProfile friend) { friendsList.remove(friend); friend.getFriendsList().remove(this); } // 省略getter/setter }
- 好友请求的补充:这个方案本身处理不了“待确认”的请求状态,得单独加个
FriendRequest实体类,存发起方、接收方、请求状态(待确认/同意/拒绝),不然没法实现“加好友需要对方同意”的逻辑。
方案二:独立好友关系模块
- 可行性:这是需要扩展功能的项目的首选,灵活性拉满。把好友关系做成独立实体,能存各种元数据(添加时间、备注、拉黑状态),而且分层清晰,符合Spring Boot的MVC架构,后续加功能也方便。
- 核心实体设计:
@Entity public class FriendRelation { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID relationId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private UserProfile user; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "friend_id") private UserProfile friend; private LocalDateTime createTime; private String friendNote; // 用户给好友的备注 private RelationStatus status; // 关系状态:待确认/已同意/已拉黑 // 省略getter/setter } // 状态枚举 public enum RelationStatus { PENDING, ACCEPTED, BLOCKED }
- 分层实现示例:
- Repository层:做基础查询
public interface FriendRelationRepository extends JpaRepository<FriendRelation, UUID> { // 查某用户对另一用户的特定状态关系 Optional<FriendRelation> findByUserUserIdAndFriendUserIdAndStatus(UUID userId, UUID friendId, RelationStatus status); // 查用户所有已同意的好友 List<FriendRelation> findByUserUserIdAndStatus(UUID userId, RelationStatus.ACCEPTED); } - Service层:封装业务逻辑
@Service public class FriendService { @Autowired private FriendRelationRepository relationRepo; @Autowired private UserProfileRepository userRepo; // 发送好友请求 public void sendFriendRequest(UUID senderId, UUID receiverId) { UserProfile sender = userRepo.findById(senderId).orElseThrow(() -> new RuntimeException("发起方用户不存在")); UserProfile receiver = userRepo.findById(receiverId).orElseThrow(() -> new RuntimeException("接收方用户不存在")); // 检查是否已有待确认请求 if (relationRepo.findByUserUserIdAndFriendUserIdAndStatus(senderId, receiverId, RelationStatus.PENDING).isPresent()) { throw new RuntimeException("已经发过好友请求了"); } FriendRelation request = new FriendRelation(); request.setUser(sender); request.setFriend(receiver); request.setCreateTime(LocalDateTime.now()); request.setStatus(RelationStatus.PENDING); relationRepo.save(request); } // 同意好友请求 public void acceptFriendRequest(UUID receiverId, UUID senderId) { // 找到发起方发的待确认请求 FriendRelation request = relationRepo.findByUserUserIdAndFriendUserIdAndStatus(senderId, receiverId, RelationStatus.PENDING) .orElseThrow(() -> new RuntimeException("没有待确认的好友请求")); // 把请求改成已同意 request.setStatus(RelationStatus.ACCEPTED); // 同时创建反向关系,方便双向查询好友列表 FriendRelation reverseRelation = new FriendRelation(); reverseRelation.setUser(request.getFriend()); reverseRelation.setFriend(request.getUser()); reverseRelation.setCreateTime(LocalDateTime.now()); reverseRelation.setStatus(RelationStatus.ACCEPTED); relationRepo.saveAll(List.of(request, reverseRelation)); } // 移除好友 public void removeFriend(UUID userId, UUID friendId) { // 删除双向的已同意关系 relationRepo.deleteByUserUserIdAndFriendUserIdAndStatus(userId, friendId, RelationStatus.ACCEPTED); relationRepo.deleteByUserUserIdAndFriendUserIdAndStatus(friendId, userId, RelationStatus.ACCEPTED); } } - Controller层:暴露接口
@RestController @RequestMapping("/api/friends") public class FriendController { @Autowired private FriendService friendService; // 发送好友请求 @PostMapping("/request") public ResponseEntity<Void> sendRequest(@RequestParam UUID senderId, @RequestParam UUID receiverId) { friendService.sendFriendRequest(senderId, receiverId); return ResponseEntity.ok().build(); } // 同意好友请求 @PostMapping("/accept") public ResponseEntity<Void> acceptRequest(@RequestParam UUID receiverId, @RequestParam UUID senderId) { friendService.acceptFriendRequest(receiverId, senderId); return ResponseEntity.ok().build(); } // 移除好友 @DeleteMapping("/{friendId}") public ResponseEntity<Void> removeFriend(@RequestParam UUID userId, @PathVariable UUID friendId) { friendService.removeFriend(userId, friendId); return ResponseEntity.ok().build(); } }
- Repository层:做基础查询
通用实现要点总结
- 先定需求范围:如果只是训练营的demo,方案一够快;如果要做更完整的社交功能,直接选方案二。
- 必须处理请求状态:不管用哪种方案,都不能直接加好友,得先通过“请求-确认”的流程,不然没法实现双向同意的逻辑。
- 数据库优化:给
FriendRelation的user_id、friend_id、status加联合索引,查询会快很多;所有关联查询都用懒加载,避免加载不必要的数据。 - 业务逻辑要严谨:
- 防止重复发送请求或添加好友;
- 移除好友时一定要处理双向关系,不然会出现“我删了对方,但对方列表里还有我”的情况;
- 加权限控制,比如只有请求接收方才能同意请求,不能让别人代操作。
内容的提问来源于stack exchange,提问作者juviscript
相关产品推荐
相关产品推荐

