좋아요 시스템 (Like System) - fitpassTeam/fitpass GitHub Wiki

좋아요 시스템 (Like System)

개요

사용자가 헬스장과 게시글에 대해 좋아요를 표현할 수 있는 토글 방식의 좋아요 시스템입니다. 토글 방식으로 구현되어 한 번 클릭하면 좋아요 추가, 다시 클릭하면 좋아요 취소가 되는 직관적인 UX를 제공합니다.

전체 구조 개요

domain/likes/
├── controller/
│   └── LikeController.java               # 좋아요 토글 API
├── service/
│   └── LikeService.java                 # 좋아요 비즈니스 로직
├── entity/
│   └── Like.java                        # 좋아요 엔티티
├── repository/
│   └── LikeRepository.java              # 좋아요 데이터 접근
└── enums/
    └── LikeType.java                    # 좋아요 대상 타입

주요 기능

토글 방식 좋아요

  • 한 번 클릭 → 좋아요 추가
  • 다시 클릭 → 좋아요 취소
  • 사용자 친화적인 UX 제공

다중 대상 지원

  • 헬스장(GYM) 좋아요
  • 게시글(POST) 좋아요
  • 확장 가능한 구조 (트레이너, 리뷰 등 추가 가능)

효율적인 데이터 구조

  • 정규화된 테이블 설계
  • 인덱스 최적화 가능
  • 집계 쿼리 지원

데이터 구조

Like 엔티티

@Entity(name = "likes")
public class Like extends BaseEntity {
    private Long id;              // 좋아요 ID
    private User user;            // 좋아요를 누른 사용자
    private LikeType likeType;    // 좋아요 대상 타입 (GYM, POST)
    private Long targetId;        // 대상 객체 ID
    private LocalDateTime createdAt; // 좋아요 생성 시간
    private LocalDateTime updatedAt; // 마지막 수정 시간
}

LikeType 열거형

public enum LikeType {
    POST,    // 게시글 좋아요
    GYM      // 헬스장 좋아요
}

좋아요 처리 플로우

1. 헬스장 좋아요 토글

POST /gyms/{gymId}/like

처리 과정:

  1. 사용자 인증: JWT 토큰 기반 사용자 확인
  2. 중복 확인: findByUserAndTargetId(user, gymId) 조회
  3. 토글 로직:
    • 기존 좋아요 없음 → 새로운 좋아요 생성
    • 기존 좋아요 있음 → 좋아요 삭제
  4. 응답: 성공 메시지 반환

2. 게시글 좋아요 토글

POST /posts/{postId}/like

처리 과정:

  • 헬스장 좋아요와 동일한 플로우
  • LikeType.POST로 구분하여 저장

핵심 비즈니스 로직

토글 방식 구현

@Transactional
public void postGymLike(Long userId, Long gymId) {
    User user = userRepository.findByIdOrElseThrow(userId);
    Optional<Like> likeOptional = likeRepository.findByUserAndTargetId(user, gymId);
    
    if(likeOptional.isEmpty()){
        // 좋아요가 없으면 생성
        Like like = Like.of(user, LikeType.GYM, gymId);
        likeRepository.save(like);
    } else {
        // 좋아요가 있으면 삭제 (토글)
        likeRepository.delete(likeOptional.get());
    }
}

사용자별 좋아요 조회

@Query("SELECT l.targetId FROM likes l WHERE l.user.id = :userId AND l.likeType = :likeType")
Set<Long> findTargetIdsByUserIdAndLikeType(@Param("userId") Long userId, @Param("likeType") LikeType likeType);

다른 도메인과의 연동

1. 헬스장 목록 조회 시 좋아요 상태 표시

// GymService.getAllGyms() 에서 활용
Set<Long> likedGymIds = (userId != null) 
    ? likeRepository.findTargetIdsByUserIdAndLikeType(userId, LikeType.GYM)
    : Collections.emptySet();

return gyms.map(gym -> {
    boolean isLiked = likedGymIds.contains(gym.getId());
    return GymResponseDto.from(gym, isLiked);  // 좋아요 상태 포함
});

2. 게시글 목록에서 좋아요 상태 표시

  • Post 도메인에서 유사한 방식으로 연동 가능
  • 사용자별 좋아요한 게시글 목록 조회

API 명세

좋아요 토글 API

Method Endpoint 설명 권한
POST /gyms/{gymId}/like 헬스장 좋아요 토글 USER
POST /posts/{postId}/like 게시글 좋아요 토글 USER

요청/응답 예시

헬스장 좋아요 토글

POST /gyms/123/like
Authorization: Bearer {JWT_TOKEN}

응답 (성공)

{
  "statusCode": 200,
  "message": "좋아요가 성공적으로 처리되었습니다.",
  "data": null
}

게시글 좋아요 토글

POST /posts/456/like
Authorization: Bearer {JWT_TOKEN}

응답 (성공)

{
  "statusCode": 200,
  "message": "좋아요가 성공적으로 처리되었습니다.",
  "data": null
}

보안 및 검증

1. 인증 검증

  • JWT 토큰 기반 사용자 인증 필수
  • 로그인하지 않은 사용자는 좋아요 불가

2. 대상 객체 존재 확인 (추가 구현 권장)

// 헬스장 존재 확인
@Transactional
public void postGymLike(Long userId, Long gymId) {
    User user = userRepository.findByIdOrElseThrow(userId);
    Gym gym = gymRepository.findByIdOrElseThrow(gymId); // 존재 확인
    
    // ... 기존 로직 ...
}

3. 중복 방지

  • findByUserAndTargetId() 쿼리로 중복 좋아요 방지
  • DB 레벨에서 UNIQUE 제약조건 추가 권장

성능 최적화

1. 인덱스 설정

-- 복합 인덱스 설정으로 조회 성능 향상
CREATE INDEX idx_likes_user_target_type ON likes(user_id, target_id, like_type);
CREATE INDEX idx_likes_target_type ON likes(target_id, like_type);

2. 배치 조회 최적화

// 여러 대상의 좋아요 상태를 한 번에 조회
@Query("SELECT l.targetId FROM likes l WHERE l.user.id = :userId AND l.targetId IN :targetIds AND l.likeType = :likeType")
Set<Long> findLikedTargetIds(@Param("userId") Long userId, @Param("targetIds") List<Long> targetIds, @Param("likeType") LikeType likeType);

주요 특징 요약

간단하고 직관적

  • 토글 방식의 사용자 친화적 UX
  • 별도의 상태 관리 없이 생성/삭제로 처리

확장 가능한 구조

  • LikeType으로 다양한 대상 지원
  • 새로운 좋아요 대상 쉽게 추가 가능

효율적인 데이터 관리

  • 정규화된 테이블 구조
  • 사용자별 좋아요 목록 빠른 조회

다른 도메인과의 연동

  • 헬스장 목록에서 좋아요 상태 표시
  • 통계 및 집계 데이터 활용 가능
⚠️ **GitHub.com Fallback** ⚠️