[9] Spring - 조별프로젝트 뉴스피드 프로젝트(JPA)

2024. 10. 25. 17:52Spring/뉴스피드 프로젝트

728x90

📁️ 프로젝트 설명

※ 깃허브 링크 : https://github.com/ii-news-feed/ii-news-feed-backend

 

GitHub - ii-news-feed/ii-news-feed-backend

Contribute to ii-news-feed/ii-news-feed-backend development by creating an account on GitHub.

github.com

간단한 SNS시스템 개발)

- 로그인

- 회원가입

- 프로필관리

- 뉴스피드 게시글관리

- 친구관리

 

프로젝트 기간 및 인원)

- 총 5일(실작업 3일)

- 인원 4명

 

사용기술)

- JAVA, Spring, JPA

 

깃 merge 방식)

- 본인 작업에 대해 issue생성

- issue번호에 따른 branch생성

- 각자 branch에서 작업 마무리 후 pr생성

- pr을 다함께 확인하면서 코드 충돌 해결 및 리뷰

- 리뷰 마무리 후 main으로 최종 merge

 

제목 내용
간단한 sns 시스템 개발 회원가입, 로그인, 프로필관리, 뉴스피드 게시글관리, 친구관리
프로젝트 기간 및 인원 총 5일(실작업 3일), 인원 4명
사용 기술 JAVA, Spring, JPA
github merge 방식 - 본인 작업에 대해 issue 생성
- issue번호에 따른 branch생성
- 각자 branch에서 작업 마무리 후 pr생성
- pr을 다함께 확인하면서 코드 충돌 해결 및 리뷰
- 리뷰 마무리 후 main으로 최종 merge

 

🛠️ 프로젝트 설계서

ERD)

 

API명세서)

 

와이어 프레임 - 일부)

 

📚 담당역할

친구요청생성)

ㄴ 요청받을사람의 기본키id와 status(상태값)을 대기(PENDING) 로 보내 저장

 

친구요청생성 - 예외처리)

ㄴ 요청을 이미 보낸경우
ㄴ 요청받을사람이 이미 나에게 요청을 한 경우
ㄴ 자기자신에게 요청하는 경우

ㄴ 요청받는 사람과 요청보내는 사람의 승인(ACCEPT)값이 대기(PENDING) 또는 승인인 경우

 

친구요청생성 - 예외처리 모두 통과후  등록 되는 조건) 

ㄴ 테이블의 데이터가 아예 0개일 경우

ㄴ 요청 받는사람이 친구요청을 거절(REJECT)했을 경우

 

※  요청 보낼때 - 로그인 고유번호(id) == fromUserId  요청생성 / 요청조회,요청응답 (나눠진 조건에따라 기준 id가 변경)

 

친구요청조회)

ㄴ 상태값이 대기(PENDING)이면서 요청받는 사람과 요청보내는사람의 고유번호(id)를 확인하여 조회


※  요청 조회시 - 로그인 고유번호(id) == toUserId

 

친구요청응답)

ㄴ 본인이 받은 요청이면서 상태값이 대기(PENDING)인 데이터만 응답할 수 있도록

 

친구요청응답 - 예외처리)

ㄴ 입력값이 ACCEPT(승인), REJECT(거절)이 아닐 경우에는 유효하지 않은 상태값으로 예외처리
ㄴ 위의 2가지 조건을 충족한 후 선택한 값이 대기(PENDING)일 경우에만 응답할 수 있도록 2차처리

(조회 조건에 들어가있지만 2차체크)


※  요청 응답시 - 로그인 고유번호(id) == toUserId

 

 

🖥️ 기능 설명

 

[ 친구 요청생성 ]

   @RestController
   @RequiredArgsConstructor
   @RequestMapping("/api/friends")
   public class FriendController {
    private final FriendService friendService;

    // 친구 요청생성 API
    @PostMapping
    public ResponseEntity<FriendResponseDto> createFriend(@RequestBody FriendRequestDto requestDto, HttpServletRequest request) {
        User fromUser = getUser(request);
        FriendResponseDto friend = friendService.createFriend(requestDto, fromUser);
        return ResponseEntity.status(HttpStatus.CREATED).body(friend);
    }

    // 로그인한 회원 정보 user 객체 추출
    public User getUser(HttpServletRequest request) {
        return (User) request.getAttribute("user");
    }
}

 

Controller)

- FriendRequestDto에서 사용자 입력값을 받아온다

(view가 없고 백엔드만 있어서 요청받을 사람 고유번호(id)와 상태값을 입력받는다.)

- HttpServletRequest를 활용하여 로그인한 회원정보를 가져온다.

- ResponseEntity는 HTTP 요청에 대한 응답을 하기위해 사용한다. 상태코드를 출력을 위해 사용했다.

- body에는 친구요청생성과 관련된 데이터들을 넣어준다.(friend)

- 아래에서 로그인한 회원 객체를 가져오기 위한 함수를 만들어준다 (getUser)

- getAttribute를 사용하여 객체를 반환한다.

 

 

@Service
public class FriendService {
    private final FriendRepository friendRepository;
    private final UserRepository userRepository;

    public FriendResponseDto createFriend(FriendRequestDto requestDto, User fromUser) {
        // 요청 보낼때 - 로그인 id == from_user_id
        Long toUserId = requestDto.getToUserId();
        if (Objects.equals(toUserId, fromUser.getId())) {
            throw new illegalargumentexception("자기 자신에게 친구요청을 보낼 수 없습니다.");
        }
        int count = friendRepository.findByToUserIdAndStatus(toUserId, fromUser.getId());
        int allCount = friendRepository.findAllById(); // 테이블 데이터 0개일 경우 체크
        int distinct = friendRepository.findByFromUserIdAndToUserId(fromUser.getId(), toUserId);
        // 내가 요청한 사람이 이미 나에게 친구요청한 경우
        if (distinct != 0 && allCount != 0) {
            throw new illegalargumentexception("상대방이 이미 나에게 친구 요청을 보냈습니다.");
        }
        // 내가 이미 요청을 보냈을 경우 그리고 상태값이 대기와 승인일 경우
        if (count != 0 && allCount != 0) {
            throw new illegalargumentexception("이미 친구 요청을 보낸 사람입니다.");
        }

        Friend friend = new Friend(requestDto.getToUserId(), requestDto.getStatus(), fromUser);
        return friendRepository.save(friend);
    }
}

Service)

- 입력받는 toUserId(요청받을사람), 객체로 받아오는 fromUser.getId()의 값을 비교하여 예외처리 진행한다.

- int count, int allCount, int distinct 3개의 변수는 repositroy에서 count()집계함수로 구해온 숫자이다.

- int count는 내가 이미 친구요청을 보낸 경우(상태값이 대기, 승인 인것도 포함된다.)를 체크한다.

- int allCount는 테이블의 갯수를 호출하며, 테이블에 데이터가 아예 0개일 경우엔 등록이 되어야하기때문에 0이 아닐경우로 처리해서 if문에 추가했다.

- int distinct는 내가 요청하려는 사람이 이미 나에게 친구요청을 한 경우를 체크한다.

- 위의 모든 예외처리를 통과하면 정상적으로 등록할 수 있는 조건은 2가지이다.

정상조건 -  테이블의 데이터가 아예 0개일 경우, 요청 받는사람이 친구요청을 거절(REJECT)했을 경우

- 등록할 수 있는 조건이 되면 JpaRepository에서 제공하는 save메서드에 등록할 값을 가지고있는 friend객체를 담는다.

 

 

@Repository
public interface FriendRepository extends JpaRepository<Friend, Long> {
    // 요청받는 사람의 승인,대기값 데이터의 개수 구하기
    String query = "SELECT COUNT(id) FROM friend WHERE to_user_id = :toUserId AND from_user_id = :fromUserId AND status != 'REJECT' ";
    @Query(value = query, nativeQuery = true)
    int findByToUserIdAndStatus(Long toUserId, Long fromUserId);

    // 친구요청이 아예 없을 경우 체크
    String queryAll = "SELECT COUNT(id) FROM friend LIMIT 0,1";
    @Query(value = queryAll, nativeQuery = true)
    int findAllById();

    // 중복체크 - 로그인한 회원 id와 요청보내는 id를 비교해서 존재하는지
    String friendId = "SELECT COUNT(id) FROM friend WHERE to_user_id = :fromUserId AND from_user_id = :toUserId";
    @Query(value = friendId, nativeQuery = true)
    int findByFromUserIdAndToUserId(Long fromUserId, Long toUserId);
}

Repository)

1. service에서 int count로 담은 변수의 쿼리이다. findByToUserIdAndStatus()

- 요청받을사람, 요청하는사람을 체크하고 마지막에 거절값이 아닌 경우로 체크한다(대기,승인을 구하기 위해)

 

2. service에서 int allCount로 담은 변수의 쿼리이다. findAllById()

-  테이블의 데이터가 0개인지만 체크하기 위해 LIMIT으로 제한하여 갯수를 확인한다.

 

3. service에서 int distinct로 담은 변수의 쿼리이다. findByFromUserIdAndToUserId()

- 내가 요청하려는 사람이 이미 나에게 요청한 경우를 체크한다.

 

※ 주의할 점)

- 요청생성을 할때의 로그인한 회원의 기준은 friend테이블에서 from_user_id컬럼이 된다.

- to_user_id컬럼은 요청받는 사람이다.

- 내가 요청하려는 사람이 이미 나에게 요청한 경우를 체크할땐 위에서 설명한 컬럼의 관계가 반대가 되어야한다.

 

※ COUNT()집계함수를 사용할 수 있게 JPA에서 제공하는 메서드가 있다고 들어서 추후에 @Query를 제거 후 일괄 변경할 예정이다. 그리고 메서드명도 count를 사용하기 때문에 그거에 알맞게 수정할 예정이다.

 

[ 친구 요청 목록 조회 ]

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/friends")
public class FriendController {
    private final FriendService friendService;
    @GetMapping
    public ResponseEntity<List<FriendResponseDto>> getFriends(HttpServletRequest request) {
        User toUser = getUser(request);
        List<FriendResponseDto> responseDto = friendService.getFriends(toUser);
        return ResponseEntity.status(HttpStatus.OK).body(responseDto);
    }
}

Controller)

- 친구 요청생성과 똑같은 형태로 진행이 되고 한가지 다른점은 List에 값을 담는다는 점이다.

- 그리고 FriendResponseDto로 조회한 값을 반환해줄 것이다.

 

 

@Service
@RequiredArgsConstructor
public class FriendService {
    private final FriendRepository friendRepository;
    private final UserRepository userRepository;
    public List<FriendResponseDto> getFriends(User toUser) {
        // 요청 조회시 - 로그인 id == to_user_id
        String status = "PENDING"; // 대기중 상태값의 친구요청만 조회
        return friendRepository.findByToUserIdOrFromUserIdAndStatus(toUser.getId(), toUser.getId(), status).stream().map(FriendResponseDto::new).toList();
    }
}

Service)

- 친구요청생성과 반대로 로그인한 정보가 to_user_id와 매핑되어야한다.

- 본인이 받은 요청이면서 대기(PENDING)값의 데이터만 조회한다.

- 본인이 받은 요청과 본인이 보낸 요청중에서도 대기중인 요청까지 보여주도록 추가로 구현했다.

(해당 부분은 어색한 느낌이 들어서 다시 빼고 본인이 받은 요청만 보여주는걸로 변경예정이다.)

 

 

@Repository
public interface FriendRepository extends JpaRepository<Friend, Long> {
    // 본인이 받은 친구 요청 목록만 조회
     List<Friend> findByToUserIdOrFromUserIdAndStatus(Long toUserId, Long fromUserId, String status);
}

Repository)

1. findByToUserIdOrFromUserIdAndStatus()

- 이름 그대로 friend테이블에서 to_user_id와 frou_user_id, status를 조회한다.

- 본인이 받은 요청과 보낸 요청이면서 대기값인 데이터만 조회되도록 하였다.

 

 

[ 친구 요청 응답 ]

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/friends")
public class FriendController {
    private final FriendService friendService;

    @PutMapping("/{friendId}")
    public Long updateFriend(@PathVariable Long friendId, @RequestBody FriendRequestDto requestDto, HttpServletRequest request) {
        User toUser = getUser(request);
        return friendService.updateFriend(friendId,requestDto,toUser);
    }
    
    public User getUser(HttpServletRequest request) {
        return (User) request.getAttribute("user");
    }
}

Controller)

- 내가 맡은 파트는 로그인 정보가 모두 필요하기 때문에 요청 응답도 동일하게 user객체의 정보를 담아준다.

- friend테이블의 고유번호 {friendid}를 @PathVariable로 담고 나머지 request와 회원객체까지 한번에 보낸다.

 

 

@Service
public class FriendService {
    private final FriendRepository friendRepository;
    @Transactional
    public Long updateFriend(Long friendId, FriendRequestDto requestDto, User toUser) {
        Friend friend = findFriend(friendId); // 
        
        // 요청 응답시 - 로그인 id == to_user_id
        int checkId = friendRepository.findByToUserIdAndStatusAndId(toUser.getId(), "PENDING", friendId);
        if (checkId == 0) {
            throw new CustomException("본인이 받은 요청에 대해서만 응답할 수 있습니다.");
        }
        String status = requestDto.getStatus();
        
        if (status.equals("ACCEPT") || status.equals("REJECT")) {
            String statusCheck = friendRepository.findAllByStatus(friendId);
            if (statusCheck.equals("PENDING")) {
                friend.update(status);
            } else {
                throw new CustomException("대기 상태값만 응답할 수 있습니다.");
            }
        } else {
            throw new CustomException("유효하지 않은 상태값입니다.");
        }
        return friendId;
    }
    
    private Friend findFriend(Long friendId) {
        return friendRepository.findById(friendId).orElseThrow(() ->
                new CustomException("비정상적인 접근입니다.)
        );
    }
}

Service)

- 친구요청생성과 반대로 로그인한 정보가 to_user_id와 매핑되어야한다.

- 친구요청의 고유번호가 올바른지 먼저 체크한다.

- 본인이 받은 친구요청인지, 상태가 대기값인지, 친구요청의 고유번호가 맞는지 체크하여 예외처리를 진행한다.

- 상태값이 승인(ACCEPT), 거절(REJECT) 2개 이외의 값이면 유효하지 않은 상태값으로 예외처리

- 상태값이 올바르면 최종으로 안에서 대기(PENDING)상태값만 응답할 수 있도록 예외처리

 

※ if문으로 코드가 길고 지저분하기도 하고, 담당튜터님이 피드백 주신 부분이 있어서 수정할 예정이다. 상태값 자체를 더 효율적으로 관리하도록 바꿀 예정이다.

 

@Repository
public interface FriendRepository extends JpaRepository<Friend, Long> {
    // 본인이 받은 친구 요청 목록만 수락&거절 할 수 있게
    String response = "SELECT COUNT(id) FROM friend WHERE to_user_id = :toUserId AND status = :status AND id = :id";
    @Query(value = response, nativeQuery = true)
    int findByToUserIdAndStatusAndId(Long toUserId, String status, Long id);
}

Repository)

1. findByToUserIdAndStatusAndId()

- 본인이 받은 요청이면서 대기값이고 friend 테이블의 id가 맞는지 체크한다.

 

※ 역시나 count를 사용중이기 때문에 @Query를 제거 후 제공하는 메서드를 사용하여 수정할 예정이다.

 

 

[ 담당파트 테스트 영상 ]

※ 조건 - user테이블에 1,2,3 3명의 회원이 존재 friend테이블은 0개의 데이터

친구요청생성)

1) 데이터 없을시 정상생성 / 자기자신 예외 / 이미보낸 요청 예외

 

 

 

2) 상대방이 이미 나에게 친구요청

 

 

3) 상태값이 거절일 경우 다시 친구요청 정상 생성

 

 

 

친구요청조회)

- 본인이 받은 요청이면서 대기값만 조회 2번으로 로그인해서 1개 데이터만 보여야함.

 

 

친구요청응답)

 

- 본인이 받은 요청이면서 대기값만 응답 가능

- 상태값 승인(ACCEPT), 거절(REJECT)이 아닐 경우 예외

- 모든 예외 처리 후 최종 정상 응답

- 비정상적인 접근(없는 데이터 접근)

 

 

 

 

 

728x90