본문 바로가기

Java/Spring

[Spring] 간단한 SNS 만들기 #3

What to do?

API 작성하기

  1. 회원가입
  2. 로그인
  3. 포스팅 조회/작성/수정/삭제
  4. 댓글 조회/작성/수정/삭제

에러처리

 

  • 에러코드 정의
    • HttpStatus와 에러메세지를 담고 잇는 에러코드 정의
@Getter
@AllArgsConstructor
public enum CustomErrorCode {
    // duplicated
    DUPLICATED_USERNAME(HttpStatus.CONFLICT, "User is duplicated..."),
    DUPLICATED_NICKNAME(HttpStatus.CONFLICT, "Nickname is duplicated..."),
    DUPLICATED_EMAIL(HttpStatus.CONFLICT, "Email is duplicated..."),
    // not found
    USERNAME_NOT_FOUND(HttpStatus.NOT_FOUND, "Username is not found..."),
    POST_NOT_FOUND(HttpStatus.NOT_FOUND, "Post is not found..."),
    COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "Comment is not found..."),
    NOT_LIKED(HttpStatus.NOT_FOUND, "Not liked..."),
    // auth failure
    INVALID_PASSWORD(HttpStatus.FORBIDDEN, "Password is wrong..."),
    NOT_GRANTED_ACCESS(HttpStatus.FORBIDDEN, "Access denied due to grant..."),
    INVALID_TOKEN(HttpStatus.FORBIDDEN, "Token is invalid..."),
    // conflict
    ALREADY_LIKED(HttpStatus.CONFLICT, "User Already like post"),
    // internal server error
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error...")
    ;
    private final HttpStatus status;
    private final String message;
}

 

  • 예외처리 
    • Usage
      • throw CustomException.of(에러코드);
      • throw CustomException.of(에러코드, 에러메세지);
@Getter
@Setter
public class CustomException extends RuntimeException {
    private CustomErrorCode code;
    private String message;

    private CustomException(CustomErrorCode code) {
        this.code = code;
        this.message = null;
    }

    private CustomException(CustomErrorCode code, String message) {
        this.code = code;
        this.message = message;
    }

    protected CustomException(){}

    public static CustomException of(CustomErrorCode code){
        return new CustomException(code);
    }

    public static CustomException of(CustomErrorCode code, String message){
        return new CustomException(code, message);
    }
}

 

  • GlobalControllerAdvice
    • CustomException 발생시 CustomErrorCode의 status 반환
    • RunTimeException발생시 INTERNAL_SERVER_ERROR 반환
@Slf4j
@RestControllerAdvice
public class GlobalControllerAdvice {
    /**
     * @param error
     * CustomException에서 정의된 에러
     * @return
     * body에 CustomErrorCode 이름
     */
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<?> applicationHandler(CustomException error){
        log.error("Error occurs... {}", error.toString());
        return ResponseEntity.status(error.getCode().getStatus())
                .body(CustomResponse.error(error.getCode().name()));
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<?> applicationHandler(RuntimeException error){
        log.error("Error occurs... {}", error.toString());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(CustomResponse.error(CustomErrorCode.INTERNAL_SERVER_ERROR.name()));
    }
}

Repository

 

  • 유저 Repository
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    Optional<UserEntity> findByUsername(String username);
    Optional<UserEntity> findByEmail(String email);
    Optional<UserEntity> findByNickname(String nickname);
}

 

  • 포스팅 Repository
@Repository
public interface PostRepository extends JpaRepository<PostEntity, Long> {
    Page<PostEntity> findAll(Pageable pageable);
    Page<PostEntity> findAllByUser(UserEntity userEntity, Pageable pageable);
}

 

  • 댓글 Repository
@Repository
public interface CommentRepository extends JpaRepository<CommentEntity, Long> {
    Page<CommentEntity> findAllByPost(PostEntity post, Pageable pageable);
}

Service 코드

 

  • UserService
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    @Value("${jwt.secret-key}") private String secretKey;
    @Value("${jwt.duration}") private Long duration;

    /**
     * 회원가입
     * @param email 이메일
     * @param username 유저명
     * @param nickname 닉네임
     * @param password 비밀번호
     * @return 회원가입 성공시 UserDto
     */
    @Transactional
    public UserDto register(String email, String username, String nickname, String password){
        // 중복체크
        checkDuplicated(_field.EMAIL, email);
        checkDuplicated(_field.NICKNAME, nickname);
        checkDuplicated(_field.USERNAME, username);
        // 비밀번호 인코딩
        String encodedPassword = bCryptPasswordEncoder.encode(password);
        return UserEntity.dto(userRepository.save(UserEntity.of(email, username, nickname, encodedPassword, RoleType.USER, null, null)));
    }

    /**
     * 로그인
     * @param username 유저명
     * @param password 비밀번호
     * @return JWT 토큰값
     */
    @Transactional(readOnly = true)
    public String login(String username, String password){
        // 존재하는 회원여부
        UserEntity user = findByUsernameOrElseThrow(username);
        // 비밀번호 일치여부 확인
        if (!bCryptPasswordEncoder.matches(password, user.getPassword())){
            throw CustomException.of(CustomErrorCode.INVALID_PASSWORD);
        }
        return JwtUtil.generateToken(username, secretKey, duration);
    }


    /**
     * 중복체크
     * @param f 중복체크할 필드
     * @param value 중복체크할 값
     */
    private void checkDuplicated(_field f, String value){
       switch (f){
           case EMAIL -> {
               // 중복체크 - 유저명, 닉네임, 이메일
               userRepository.findByUsername(value).ifPresent(it->{
                   throw CustomException.of(
                           CustomErrorCode.DUPLICATED_USERNAME,
                           String.format("Username [%s] is duplicated...", value)
                   );
               });
           }
           case USERNAME -> {
               userRepository.findByEmail(value).ifPresent(it->{
                   throw CustomException.of(
                           CustomErrorCode.DUPLICATED_USERNAME,
                           String.format("Email [%s] is duplicated...", value)
                   );
               });
           }
           case NICKNAME -> {
               userRepository.findByNickname(value).ifPresent(it->{
                   throw CustomException.of(
                           CustomErrorCode.DUPLICATED_NICKNAME,
                           String.format("Nickname [%s] is duplicated...", value)
                   );
               });
           }
       }
    }

    /**
     * 중복체크할 필드
     */
    private enum _field{
        USERNAME, NICKNAME, EMAIL;
    }

    @Transactional(readOnly = true)
    public UserEntity findByUsernameOrElseThrow(String username){
        return userRepository.findByUsername(username)
                .orElseThrow(()->{throw CustomException.of(CustomErrorCode.USERNAME_NOT_FOUND);});
    }
}

 

  • PostService
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class PostController {
    private final PostService postService;

    // 포스팅 단건조회
    @GetMapping("/post/{postId}")
    public CustomResponse<PostDto> getPost(@PathVariable Long postId){
        return CustomResponse.success(postService.getPost(postId));
    }


    // 포스팅 페이지 조회
    @GetMapping("/post")
    public CustomResponse<Page<PostDto>> getPostsByUser(@PageableDefault Pageable pageable, @RequestParam("username") String username){
        if (username==null){
            return CustomResponse.success(postService.getPosts(pageable));
        }
        return CustomResponse.success(postService.getPostsByUser(pageable, username));
    }

    // 포스팅 작성
    @PostMapping("/post")
    public CustomResponse<Long> createPost(@RequestBody CreatePostRequest req, Authentication authentication){
        return CustomResponse.success(postService.createPost(req.getTitle(), req.getContent(), authentication.getName()));
    }

    // 포스팅 수정
    @PutMapping("/post/{postId}")
    public CustomResponse<Long> modifyPost(@PathVariable Long postId, @RequestBody ModifyPostRequest req, Authentication authentication){
        return CustomResponse.success(postService.modifyPost(postId, req.getTitle(), req.getContent(), authentication.getName()));
    }

    // 포스팅 삭제
    @DeleteMapping("/post/{postId}")
    public CustomResponse<Long> deletePost(@PathVariable Long postId, Authentication authentication){
        return CustomResponse.success(postService.deletePost(postId, authentication.getName()));
    }

    // 댓글 조회
    @GetMapping("/comment/{postId}")
    public CustomResponse<Page<CommentDto>> getComment(@PathVariable Long postId, @PageableDefault Pageable pageable){
        return CustomResponse.success(postService.getComments(postId, pageable));
    }

    // 댓글 작성
    @PostMapping("/comment")
    public CustomResponse<CommentDto> createPost(@RequestBody CreateCommentRequest req, Authentication authentication){
        return CustomResponse.success(postService.createComment(req.getPostId(), req.getContent(), authentication.getName()));
    }

    // 댓글 수정
    @PutMapping("/comment")
    public CustomResponse<CommentDto> modifyPost(@RequestBody ModifyCommentRequest req, Authentication authentication){
        return CustomResponse.success(postService.modifyComment(req.getPostId(), req.getCommentId(), req.getContent(), authentication.getName()));
    }

    // 댓글 삭제
    @DeleteMapping("/comment")
    public CustomResponse<Void> deletePost(@RequestBody DeleteCommentRequest req, Authentication authentication){
        postService.deleteComment(req.getPostId(), req.getCommentId(), authentication.getName());
        return CustomResponse.success();
    }

    // 좋아요 & 싫어요 개수 가져오기
    @GetMapping("/like/{postId}")
    public CustomResponse<GetLikeResponse> getLikeCount(@PathVariable Long postId){
        return CustomResponse.success(GetLikeResponse.from(postId, postService.getLikeCount(postId)));
    }

    // 좋아요 & 싫어요 요청
    @PostMapping("/like")
    public CustomResponse<Void> likePost(@RequestBody LikePostRequest req, Authentication authentication){
        postService.likePost(req.getPostId(), req.getLikeType(), authentication.getName());
        return CustomResponse.success();
    }
}

 


Controller

 

  • Controller 응답
    • 성공시
      • return CustomResponse.success();
      • return CustomResponse.success(result);
@Getter
@Setter
public class CustomResponse<T> {
    private String statusCode;
    private T result;

    private CustomResponse(String statusCode, T result) {
        this.statusCode = statusCode;
        this.result = result;
    }

    protected CustomResponse(){}

    public static <T> CustomResponse<T> success() {
        return new CustomResponse<T>("SUCCESS", null);
    }

    public static <T> CustomResponse<T> success(T result) {
        return new CustomResponse<T>("SUCCESS", result);
    }

    public static CustomResponse<Void> error(String resultCode) {
        return new CustomResponse<Void>(resultCode, null);
    }

    public static String json(CustomResponse res){
        if (res.result == null) {
            return "{" +
                    "\"resultCode\":" + "\"" + res.statusCode + "\"," +
                    "\"result\":" + null + "}";
        }
        return "{" +
                "\"resultCode\":" + "\"" + res.statusCode + "\"," +
                "\"result\":" + "\"" + res.result.toString() + "\"" + "}";
    }
}

 

  • UserController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/user")
public class UserController {
    private final UserService userService;

    /**
     * 회원가입
     * @Param - username nickname email password
     * @Return - nickname
     */
    @PostMapping("/register")
    public CustomResponse<String> register(@RequestBody RegisterRequest req){
        return CustomResponse.success(userService.register(req.getEmail(), req.getUsername(), req.getNickname(), req.getPassword()).getNickname());
    }
    /**
     * 로그인
     * @Param - username password
     * @Return - Authorization token (JWT)
     */
    @PostMapping("/login")
    public CustomResponse<String> login(@RequestBody LoginRequest req){
        return CustomResponse.success(userService.login(req.getUsername(), req.getPassword()));
    }
}

 

  • PostController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class PostController {
    private final PostService postService;

    // 포스팅 단건조회
    @GetMapping("/post/{postId}")
    public CustomResponse<PostDto> getPost(@PathVariable Long postId){
        return CustomResponse.success(postService.getPost(postId));
    }

    // 포스팅 페이지 조회
    @GetMapping("/post")
    public CustomResponse<Page<PostDto>> getPosts(@PageableDefault Pageable pageable){
        return CustomResponse.success(postService.getPosts(pageable));
    }

    // 포스팅 페이지 조회 by 유저
    @GetMapping("/post")
    public CustomResponse<Page<PostDto>> getPostsByUser(@PageableDefault Pageable pageable, @RequestParam("username") String username){
        return CustomResponse.success(postService.getPostsByUser(pageable, username));
    }

    // 포스팅 작성
    @PostMapping("/post")
    public CustomResponse<Long> createPost(@RequestBody CreatePostRequest req, Authentication authentication){
        return CustomResponse.success(postService.createPost(req.getTitle(), req.getContent(), authentication.getName()));
    }

    // 포스팅 수정
    @PutMapping("/post/{postId}")
    public CustomResponse<Long> modifyPost(@PathVariable Long postId, @RequestBody ModifyPostRequest req, Authentication authentication){
        return CustomResponse.success(postService.modifyPost(postId, req.getTitle(), req.getContent(), authentication.getName()));
    }

    // 포스팅 삭제
    @DeleteMapping("/post/{postId}")
    public CustomResponse<Long> deletePost(@PathVariable Long postId, Authentication authentication){
        return CustomResponse.success(postService.deletePost(postId, authentication.getName()));
    }

    // 댓글 조회
    @GetMapping("/comment/{postId}")
    public CustomResponse<Page<CommentDto>> getComment(@PathVariable Long postId, @PageableDefault Pageable pageable){
        return CustomResponse.success(postService.getComments(postId, pageable));
    }

    // 댓글 작성
    @PostMapping("/comment")
    public CustomResponse<CommentDto> createPost(@RequestBody CreateCommentRequest req, Authentication authentication){
        return CustomResponse.success(postService.createComment(req.getPostId(), req.getContent(), authentication.getName()));
    }

    // 댓글 수정
    @PutMapping("/comment")
    public CustomResponse<CommentDto> modifyPost(@RequestBody ModifyCommentRequest req, Authentication authentication){
        return CustomResponse.success(postService.modifyComment(req.getPostId(), req.getCommentId(), req.getContent(), authentication.getName()));
    }

    // 댓글 삭제
    @DeleteMapping("/comment")
    public CustomResponse<Void> deletePost(@RequestBody DeleteCommentRequest req, Authentication authentication){
        postService.deleteComment(req.getPostId(), req.getCommentId(), authentication.getName());
        return CustomResponse.success();
    }
}

 

 

 

'Java > Spring' 카테고리의 다른 글

[Spring] 간단한 SNS 만들기 #5  (0) 2023.01.15
[Spring] 간단한 SNS 만들기 #4  (3) 2023.01.01
[Spring] 간단한 SNS 만들기 #2  (0) 2022.12.22
[Spring] 간단한 SNS 만들기 #1  (0) 2022.12.22
[Spring] 길찾기 서비스 #6  (0) 2022.12.11