2024. 10. 25. 17:52ㆍSpring/뉴스피드 프로젝트
📁️ 프로젝트 설명
※ 깃허브 링크 : https://github.com/ii-news-feed/ii-news-feed-backend
간단한 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)이 아닐 경우 예외
- 모든 예외 처리 후 최종 정상 응답
- 비정상적인 접근(없는 데이터 접근)
'Spring > 뉴스피드 프로젝트' 카테고리의 다른 글
[11] Spring - 친구요청 관련 쿼리문 트러블 슈팅 (15) | 2024.10.27 |
---|---|
[10] Spring - 뉴스피드 프로젝트 GitHub 협업방식 및 코드충돌해결 (4) | 2024.10.27 |