본문 바로가기

내일배움캠프

아웃소싱 프로젝트 4일차

우선 지난 주말에 모여서 회의를 진행했었다.

우선 팀장인 내가 팀장으로서 너무 경솔한 행동을 하였고 팀원들과 충분한 소통을 하지 않고 독단적인 판단을 하였다.

 

지난주 담당 튜터님께 요약하자면 팀 규모와 작업기간에 비해 프로젝트 스케일이 너무 작다는 피드백을 받았다. 필자도 계속 인지는 하고 있었다. 프로젝트를 진행하면서 뭔가 시간을 버리는 듯한 느낌을 너무 많이 받았다. 프로젝트 내용만 보면 지난 프로젝트에서 이미 했던 것들인데 왜 여기서 또 하면서 시간 낭비를 하고 있지? 라는 생각이 마음속 깊은 곳에서 계속 올라왔다. 근데 담당 튜터님께 받은 피드백을 결정타로 마음이 점점 초조해져 갔다. 이대로는 안됀다. 이렇게 프로젝트를 하는데 시간을 낭비할 수는 없다. 뭔가 이전 프로젝트에는 없던 새로운 걸 시도해봐야 한다. 라는 강박감이 점점 자리를 잡아갔다.

 

결국 이 강박감이 일을 저지르고 말았다. 튜터님께 결국 추가기능을 다 구현하는게 목표다라고 아무런 상의 없이 호언장담을 해버리고 뒤늦게 팀원들에게 주말에 모여주실 수 있냐고 이야기가 나온 것이다.

 

주말에 모여서는 필자는 결국 이에 대한 질타를 받았다. 팀원들은 이런 독단적인 선택을 매우 싫어하는것 같았다. 팀원들이 모여서 나에게 공통적으로 한 이야기가 뭐냐면 프로젝트의 진행속도가 느리다는 건 인정하지만 프로젝트의 일정 자체는 상당히 빠듯하게 다가왔다는 것이다. 필수 기능들을 소화하는 것도 여유로운 건 아니라서 여기서 추가 기능을 더 구현하는건 어렵다는 의견들을 내 주었다. 그래서 필자는 필자만이라도 원하는 추가기능을 구현해도 괜찮냐고 물었더니 그건 상관없다고 하고 싶은 기능들을 구현해도 된다고 해 주었다. 마지막으로 팀원 여러분들을 계속 몰아세워서 죄송하고 팀장으로서 팀원 여러분들과 충분한 소통을 하지 않고 독선적인 선택을 내린 점 정말로 죄송하다는 사과를 올리면서 회의를 마무리 하였다.

 

회의가 마무리 된 후 주말간 카카오 소셜로그인과 좋아요 기능을 구현하였다.

 

oauth2 액세스토큰 요청코드:

private String getToken(String code) throws JsonProcessingException {
    //log.info("인가코드 : " + code);
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("https://kauth.kakao.com")
            .path("/oauth/token")
            .encode()
            .build()
            .toUri();

    // HTTP Header 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    // HTTP Body 생성
    MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
    body.add("grant_type", "authorization_code");
    body.add("client_id", "ee92cf31b98dc0d8c9b20251871cf82e");
    body.add("redirect_uri", "http://localhost:8080/api/users/oauth");
    body.add("code", code);

    RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
            .post(uri)
            .headers(headers)
            .body(body);

    // HTTP 요청 보내기
    ResponseEntity<String> response = restTemplate.exchange(
            requestEntity,
            String.class
    );

    // HTTP 응답 (JSON) -> 액세스 토큰 파싱
    JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
    return jsonNode.get("access_token").asText();
}

액세스토큰으로 사용자 정보 가져오는 코드:

private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
    //log.info("accessToken : " + accessToken);
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("https://kapi.kakao.com")
            .path("/v2/user/me")
            .encode()
            .build()
            .toUri();

    // HTTP Header 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + accessToken);
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
            .post(uri)
            .headers(headers)
            .body(new LinkedMultiValueMap<>());

    // HTTP 요청 보내기
    ResponseEntity<String> response = restTemplate.exchange(
            requestEntity,
            String.class
    );

    JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
    Long id = jsonNode.get("id").asLong();
    String nickname = jsonNode.get("properties")
            .get("nickname").asText();

    //log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
    return new KakaoUserInfoDto(id, nickname);
}

 

User테이블에 카카오 사용자 정보를 기반으로 회원을 등록하는 코드:

private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
    // DB 에 중복된 Kakao Id 가 있는지 확인
    Long kakaoId = kakaoUserInfo.getId();
    User kakaoUser = userRepository.findByUserUid(kakaoId.toString()).orElse(null);

    if (kakaoUser == null) {
            // 신규 회원가입
            // password: random UUID
            String password = UUID.randomUUID().toString();
            String encodedPassword = passwordEncoder.encode(password);

            kakaoUser = new User(kakaoUserInfo, encodedPassword, UserStatus.KAKAO);

        userRepository.save(kakaoUser);
    }
    return kakaoUser;
}
private void updateRefreshToken(User user, String refreshToken) {
    String originalRefreshToken = jwtUtil.refreshTokenSubstring(refreshToken);
    user.updateRefreshToken(originalRefreshToken);
    userRepository.save(user);
}

 

여기까지가 카카오 소셜로그인의 주요 로직들이다.

 

public ResponseEntity<String> toggleBoardLike(Long userId, Long contentId) {
    validateBoardLike(userId, contentId);
    Optional<Like> likeOptional = findLike(userId, contentId, LikeType.BOARD);

    if (likeOptional.isPresent()) {
        likeRepository.delete(likeOptional.get());
        boardService.decreaseBoardLike(contentId);
        return ResponseEntity.ok("Like removed" + HttpStatus.OK.value());
    } else {
        Like like = new Like(userId, contentId, LikeType.BOARD);
        likeRepository.save(like);
        boardService.increaseBoardLike(contentId);
        return ResponseEntity.ok("Like added" + HttpStatus.OK.value());
    }
}

좋아요 기능 주요 로직이다. 좋아요를 하지 않았다면 likes엔티티에 좋아요가 등록이 되고 이미 등록되있다면 좋아요를 해제하는 방식의 로직이다.

 

public void validateBoardLike(Long userId, Long contentId) {
    Board board = boardRepository.findById(contentId).orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."));
    if(board.getUser().getId().equals(userId)) {
        throw new ForbiddenException("해당 게시글의 작성자입니다.");
    }
}
public void validateCommentLike(Long userId, Long contentId) {
    Comment comment = commentRepository.findById(contentId).orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다."));
    if(comment.getUser().getId().equals(userId)) {
        throw new ForbiddenException("해당 댓글의 작성자입니다.");
    }
}

게시글이나 댓글을 존재하는지 검증하고 좋아요 누른 사람이 작성자 본인인지 검증하는 로직이다.

 

public void decreaseCommentLike(Long contentId) {
    Comment comment = commentRepository.findById(contentId).orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다."));
    comment.decreaseLike();
}

public void increaseCommentLike(Long contentId) {
    Comment comment = commentRepository.findById(contentId).orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다."));
    comment.increaseLike();
}
@Transactional
public void decreaseBoardLike(Long contentId) {
    Board board = boardRepository.findById(contentId).orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."));
    board.decreaseLike();
}

@Transactional
public void increaseBoardLike(Long contentId) {
    Board board = boardRepository.findById(contentId).orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."));
    board.increaseLike();
}

만약 좋아요 api가 호출되면 board와 comment 엔티티의 좋아요 숫자가 변한다.

 

이렇게 주말간 개발을 끝으로 프로젝트의 개발은 마무리되었다.

 

월요일에는 모여서 발표자와 기능시연 영상 촬영할 사람을 정했다. 기능시연 영상은 필자가 촬영했다.

영상을 촬영해서 편집하는 것으로 하루를 마무리했다.

'내일배움캠프' 카테고리의 다른 글

JPA 심화과정  (0) 2024.07.10
querydsl로 동적 정렬 구현  (0) 2024.07.05
아웃소싱 프로젝트 5일차(KPT회고)  (0) 2024.06.25
아웃소싱 프로젝트 3일차  (0) 2024.06.24
아웃소싱 프로젝트 2일차  (0) 2024.06.21