본문 바로가기

2023년/멋쟁이사자처럼 팀프로젝트

[팀프로젝트] SpringBoot 좋아요 기능 구현 ( UI 는 없음 )

SpringBoot RestAPI 좋아요 기능 구현 ( UI 는 없음 )

이슈로 좋아요 기능 구현 역할

환경 설정

목표 ) 1개의 게시글을 1명의 유저가 좋아요를 하는 기능을 개발

 

IDE : IntelliJ 2023
Language : Java 11
SpringBoot : 2.7.7
외 내용 Gradle 참고!

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.7'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'teamproject'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.projectlombok:lombok:1.18.22'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'


    // Security
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.security:spring-security-test'

    // JWT
    implementation 'io.jsonwebtoken:jjwt:0.9.1'

    // Swagger
    implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '3.0.0'
    implementation 'io.springfox:springfox-boot-starter:3.0.0'

}

tasks.named('test') {
    useJUnitPlatform()
}

 

이번 프로젝트 이슈 중 하나 좋아요 기능을 맡게 됨

 

하하..


ㅇㅎ
디렉토리 구조

위에 디렉토리 구조에서 중요하게 볼 것은 

Controller - Service - Repository 와 Entity(Like) DTO(LikeResponse) 다

 

좋아요 기능을 구현하기 위해 다음과 같은 로직을 거친다.

  • 로그인을 실시한 유저가
  • 해당 게시글에 좋아요 요청
  • 요청한 좋아요가 존재하는지 검사
  • 있으면 좋아요 취소 / 없으면 좋아요

엄청 단순하게 구현했다. 추후 더 좋은 방법이 떠오르면 리펙토링 할 예정이다.

 

소스코드

Like.java

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Table(name = "likes")
public class Like {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
//    private AlarmType alarm;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "crew_id")
    private Crew crew;

}
like 는 mysql 예약어 이므로 꼭 @Table 어노테이션을 사용하여 테이블 이름을 달리 설정 해 줘야한다.
@OneToMany(mappedBy = "user")
private List<Like> likes = new ArrayList<>();
@OneToMany(mappedBy = "crew")
private List<Like> likes = new ArrayList<>();
  • 1명이 여러 crew 를 좋아요 할 수 있고,
  • 1개의 crew 가 여러 user 에게 좋아요를 받을 수 있다.

저장 되는 방식은 

id user_id crew_id
1 1 2
2 1 3
3 1 4
4 2 2
  • user_id 가 해당 게시글 (crew_id) 를 좋아요 함
  • user_id 1인 사람이 2, 3, 4 게시글을 좋아요
  • user_id 2 인 사람이 2 게시글을 좋아요

 

LikeController.java

@RestController
@RequestMapping("/api/v1/crews")
@RequiredArgsConstructor
public class LikeController {
    private final LikeService likeService;
    @PostMapping("/{crewId}/like")
    public Response<LikeResponse> likeCrew(@PathVariable Long crewId, Authentication authentication){
        LikeResponse goodResponse = likeService.goodCrew(crewId,authentication.getName());
        return Response.success(goodResponse);
    }
}
POST /api/v1/crews/{crewId}/like

1. 요청이 오면  authentication 에서 로그인한 정보에 대해서 캐치함 > 로그인 전제 하 진행

2. @PathVariable로 받은 crewId 값을 service 레이어로 넘김

 

LikeService.java

@Service
@RequiredArgsConstructor
public class LikeService {
    private final LikeRepository likeRepository;
    private final CrewRepository crewRepository;
    private final UserRepository userRepository;

    public LikeResponse goodCrew(Long crewId, String userName){
        User user = userRepository.findByUserName(userName).orElseThrow(()->new AppException(ErrorCode.USERID_NOT_FOUND,ErrorCode.USERID_NOT_FOUND.getMessage()));
        Crew crew = crewRepository.findById(crewId).orElseThrow(()->new AppException(ErrorCode.CREW_NOT_FOUND,ErrorCode.CREW_NOT_FOUND.getMessage()));
        LikeResponse goodResponse = new LikeResponse();
        if(user.getLikes().stream().anyMatch(like -> like.getCrew().equals(crew))){
            likeRepository.deleteByUserAndCrew(user,crew);
            goodResponse.setMessage("좋아요 취소");
        } else {
            likeRepository.save(Like.builder().crew(crew).user(user).build());
            goodResponse.setMessage("좋아요 성공");
        }
        return goodResponse;
    }
}

1. 서비스 레이어에서는 컨트롤러로 부터 받은 게시글의 id 와 로그인한 유저 정보를 가지고 repository에 있는지 확인

2. findBy ** 으로 해당 엔티티가 있는지 찾고 없으면 에러를 발생시킨다. (에러는 custom 이여서 선택)

3. User 엔티티의 OneToMany로 매핑된 Like (ArrayList) 를 stream 메소드를 통해 crew 값이 있는지 판단하여 로직수행

  • 람다식 형식을 공부할 필요성을 느낌
allMatch() 모든 요소들이 매개값(Predicate)으로 주어진 조건을 만족하는지 조사
anyMatch() 최소한 한 개의 요소가 주어진 조건에 만족하는지 조사
noneMatch() 모든 요소들이 주어진 조건을 만족하지 않는지 조사

 

 

LikeRepository.java

public interface LikeRepository extends JpaRepository<Like, Long> {
    void deleteByUserAndCrew(User user, Crew crew);
}

1. 삭제 부분에 대해서 해당 정보를 가진 Like 엔티티 삭제 ( 추후 Soft Delete 로 구현 )

 


실행 화면

 

좋아요 성공 사례!

 

좋아요 취소 사례!

확인할때 Autentication 을 사용하기 때문에 SpringSecurityContext에 값이 들어 올 수 있도록 설정을 해주자

ex) 위의 방식은 Jwt 토큰을 넘겨주는 방식을 사용함

 

 

팀 프로젝트가 순조롭게 진행되고 있다.. 순조로워서 더욱 불안한 감정이 든다..

하지만 중요한건 꺾이지 않는 마음