대규모 트래픽 게시판 구축 시리즈 #9: 게시판 API

2024. 9. 7. 20:33·프레임워크/자바 스프링

1. 개요

이번 구현에서 다루는 주요 기능은 다음과 같습니다:

  • 게시글 CRUD: 사용자가 게시글을 생성, 조회, 수정, 삭제할 수 있도록 API를 설계.
  • 댓글 기능: 각 게시글에 댓글을 달고, 댓글을 수정, 삭제하는 기능 구현.
  • 태그 관리: 게시글에 태그를 추가하고, 태그를 수정 및 삭제하는 기능.

각 기능은 Controller, Service, Mapper, DTO를 사용해 계층 구조로 분리되었으며, MyBatis를 통해 데이터베이스와 상호작용합니다.


2. PostController: 게시글 관련 API 구현

PostController는 게시글 관리 엔드포인트를 정의하고, 사용자의 요청을 처리하여 게시글의 생성, 조회, 수정, 삭제 기능을 제공합니다. 이를 통해 게시판의 핵심 기능인 게시글 작성과 조회를 관리할 수 있습니다.

2.1 게시글 등록

게시글 등록은 POST /posts 엔드포인트에서 처리됩니다. 사용자가 로그인한 상태에서 게시글 제목, 내용, 카테고리 ID 등을 입력하면 게시글이 데이터베이스에 저장됩니다.

@PostMapping
public ResponseEntity<CommonResponse<PostDTO>> registerPost(
        @RequestBody @Valid PostRequest postRequest, HttpSession session) {
    UserDTO user = getAuthenticatedUser(session);
    if (user == null) {
        return handleUnauthorized();
    }
    PostDTO result = postService.register(user.getId(), postRequest);
    return createResponse(HttpStatus.CREATED, "POST_CREATED", "게시글이 성공적으로 등록되었습니다.", result);
}
  • Session을 통한 인증 처리: SessionUtil을 사용하여 현재 로그인한 사용자의 ID를 가져와 게시글을 작성합니다.
  • 데이터 검증: 입력된 게시글 데이터는 서비스 계층에서 검증한 후, 유효할 경우 등록을 진행합니다.
  • 결과 반환: 등록된 게시글의 정보를 PostDTO로 클라이언트에 반환합니다.

2.2 게시글 조회

로그인한 사용자는 자신이 작성한 게시글 목록을 조회할 수 있습니다. GET /posts/my-posts API는 페이지네이션을 통해 데이터를 제공합니다.

@GetMapping("/my-posts")
@LoginCheck(roles = {"DEFAULT", "ADMIN"})
public ResponseEntity<CommonResponse<Map<String, Object>>> myPostInfo(
        @RequestParam(value = "page", defaultValue = "1") Long page,
        @RequestParam(value = "size", defaultValue = "10") Long size,
        HttpSession session) {
    UserDTO user = getAuthenticatedUser(session);
    if (user == null) {
        return handleUnauthorized();
    }
    Map<String, Object> result = postService.getMyPosts(user.getId(), page, size);
    return createResponse(HttpStatus.OK, "POSTS_RETRIEVED", "내 게시글 목록을 성공적으로 조회했습니다.", result);
}
  • 페이지네이션 지원: page와 size 파라미터를 통해 원하는 페이지의 게시글 목록을 가져옵니다.
  • 권한 검사: LoginCheck 어노테이션을 사용하여 로그인된 사용자만 자신의 게시글 목록을 조회할 수 있도록 제어합니다.

게시글 수정과 삭제에 대한 설명을 추가합니다. 게시글을 수정하거나 삭제하는 작업은 게시판에서 매우 중요한 기능입니다. 이 기능들은 인증된 사용자만 접근할 수 있도록 보호되며, 사용자 권한에 따라 수정과 삭제가 가능합니다.


2.3 게시글 수정 (Update)

게시글 수정 기능은 PATCH /posts/{postId} 엔드포인트에서 처리됩니다. 사용자는 작성한 게시글을 수정할 수 있으며, 제목, 내용, 카테고리 등의 필드를 수정할 수 있습니다.

@PatchMapping("/{postId}")
@LoginCheck(roles = {"DEFAULT", "ADMIN"})
public ResponseEntity<CommonResponse<PostDTO>> updatePost(
        @PathVariable(value = "postId") Long postId,
        @RequestBody @Valid PostRequest postRequest,
        HttpSession session) {
    UserDTO user = getAuthenticatedUser(session);
    if (user == null) {
        return handleUnauthorized();
    }
    PostDTO postDTO = postService.updatePost(postId, user.getId(), postRequest);
    return createResponse(HttpStatus.OK, "POST_UPDATED", "게시글이 성공적으로 수정되었습니다.", postDTO);
}
  • API 설명:
    • 이 엔드포인트는 로그인한 사용자가 특정 게시글의 ID를 기반으로 게시글의 일부 필드를 수정할 수 있게 합니다.
    • @LoginCheck 어노테이션을 통해 사용자가 로그인 상태인지 확인하며, 권한이 없는 사용자는 접근할 수 없습니다.
  • Service 계층 처리:
    • updatePost(Long postId, Long userId, PostRequest postRequest) 메서드에서 수정할 게시글을 검색하고, 요청된 데이터로 필드를 업데이트합니다.
@Override
@Transactional
@CacheEvict(value = "getProducts", allEntries = true)
public PostDTO updatePost(Long postId, Long userId, PostRequest requestPostDTO) {
    PostDTO existingPost = getAndValidatePost(postId, userId);
    updatePostFields(existingPost, requestPostDTO);
    try {
        postMapper.updatePost(existingPost);
    } catch (Exception e) {
        log.error("Error updating post: postId={}", postId, e);
        throw new RuntimeException("Failed to update post", e);
    }
    return postMapper.getPost(postId);
}
  • 게시글 유효성 검사: getAndValidatePost(Long postId, Long userId) 메서드를 사용하여 해당 게시글이 존재하는지 확인하고, 해당 사용자가 수정할 권한이 있는지 확인합니다.
  • 필드 업데이트: 필드가 null이 아니거나 요청된 데이터가 있을 경우에만 기존 데이터를 덮어씁니다.

2.4 게시글 삭제 (Delete)

게시글 삭제는 DELETE /posts/{postId} 엔드포인트에서 처리됩니다. 사용자는 작성한 게시글을 삭제할 수 있으며, 삭제된 게시글은 데이터베이스에서 완전히 제거됩니다.

@DeleteMapping("/{postId}")
@LoginCheck(roles = {"DEFAULT", "ADMIN"})
public ResponseEntity<CommonResponse<Void>> deletePost(
        @PathVariable(value = "postId") Long postId,
        HttpSession session) {
    UserDTO user = getAuthenticatedUser(session);
    if (user == null) {
        return handleUnauthorized();
    }
    postService.deletePost(user.getId(), postId);
    return createResponse(HttpStatus.OK, "POST_DELETED", "게시글이 성공적으로 삭제되었습니다.", null);
}
  • API 설명:
    • 이 엔드포인트는 로그인한 사용자가 작성한 게시글을 삭제할 수 있도록 합니다.
    • 역시 @LoginCheck 어노테이션을 사용하여 사용자가 로그인 상태인지, 삭제할 권한이 있는지를 확인합니다.
  • Service 계층 처리:
    • deletePost(Long userId, Long postId) 메서드에서 게시글을 삭제합니다. 게시글에 연결된 댓글, 태그 등도 삭제하는 과정을 거칩니다.
@Override
@Transactional
@CacheEvict(value = "getProducts", allEntries = true)
public void deletePost(Long userId, Long postId) {
    if (userId <= 0 || postId <= 0) {
        throw new IllegalArgumentException("Invalid userId or postId");
    }

    try {
        // 게시글 존재 여부 확인
        PostDTO post = postMapper.getPost(postId);
        if (post == null) {
            throw new RuntimeException("Post not found with id: " + postId);
        }

        // 관련된 태그 및 댓글 삭제
        postMapper.deletePostTagByPostId(postId);
        commentMapper.deleteCommentsByPostId(postId);

        // 게시글 삭제
        postMapper.deletePost(postId);

        log.info("Successfully deleted post with id: {}", postId);
    } catch (DataAccessException e) {
        log.error("Database error occurred while deleting post: postId={}", postId, e);
        throw new RuntimeException("Database error occurred while deleting post", e);
    } catch (Exception e) {
        log.error("Error deleting post: postId={}", postId, e);
        throw new RuntimeException("Failed to delete post", e);
    }
}
  • 연관 데이터 삭제:
    • 게시글을 삭제하기 전에 해당 게시글과 연결된 댓글과 태그를 먼저 삭제합니다.
    • postMapper.deletePostTagByPostId(postId)와 commentMapper.deleteCommentsByPostId(postId)를 통해 게시글과 연관된 데이터를 먼저 제거한 후, 게시글 자체를 삭제합니다.

3. 댓글(Comment) 기능 구현

댓글 기능은 게시글에 추가되는 사용자 간의 상호작용을 담당합니다. 사용자는 게시글에 댓글을 작성하고, 이를 수정 또는 삭제할 수 있습니다. 이 기능은 PostService와 연계되어 있으며, CommentDTO를 통해 데이터베이스에 저장됩니다.

3.1 댓글 등록

댓글은 특정 게시글에 달리는 형태로, 사용자가 댓글을 작성할 수 있도록 API를 제공합니다.

@PostMapping("/comments")
@LoginCheck(roles = {"DEFAULT", "ADMIN"})
public ResponseEntity<CommonResponse<CommentDTO>> registerComment(
        @RequestBody @Valid CommentRequest commentRequest,
        HttpSession session) {
    UserDTO user = getAuthenticatedUser(session);
    if (user == null) {
        return handleUnauthorized();
    }
    CommentDTO result = postService.registerComment(user.getId(), commentRequest);
    return createResponse(HttpStatus.CREATED, "COMMENT_CREATED", "댓글이 성공적으로 등록되었습니다.", result);
}
  • 댓글 작성 처리: 댓글 요청 데이터는 CommentRequest 객체로 받아 서비스 계층으로 전달됩니다.
  • 결과 반환: 등록된 댓글 정보를 클라이언트에 CommentDTO 형태로 반환합니다.

3.2 댓글 수정 및 삭제

  • 댓글은 PATCH /comments/{commentId}로 수정할 수 있고, DELETE /comments/{commentId}로 삭제할 수 있습니다. 각 API는 LoginCheck 어노테이션을 통해 로그인한 사용자만 접근 가능하도록 설정됩니다.

4. 태그(Tag) 기능 구현

태그는 게시글을 분류하는 중요한 요소로, 게시글에 태그를 추가하거나 수정, 삭제하는 기능을 제공합니다. 각 게시글은 여러 개의 태그를 가질 수 있으며, 이를 통해 사용자는 원하는 주제에 맞는 게시글을 쉽게 찾아볼 수 있습니다.

4.1 태그 등록

@PostMapping("/tags")
@LoginCheck(roles = {"DEFAULT", "ADMIN"})
public ResponseEntity<CommonResponse<Void>> registerTag(
        @RequestBody @Valid TagRequest tagRequest,
        HttpSession session) {
    UserDTO user = getAuthenticatedUser(session);
    if (user == null) {
        return handleUnauthorized();
    }
    postService.registerTag(user.getId(), tagRequest);
    return createResponse(HttpStatus.CREATED, "TAG_CREATED", "태그가 성공적으로 등록되었습니다.", null);
}
  • 태그 생성: 사용자가 입력한 태그 이름과 URL을 데이터베이스에 저장합니다.
  • 중복 확인: 이미 등록된 태그가 있는지 확인 후, 없는 경우 새로운 태그를 등록합니다.

5. PostService와 서비스 계층 로직

PostService는 게시글, 댓글, 태그와 관련된 비즈니스 로직을 처리합니다. 이 계층에서는 데이터 검증, 예외 처리, 그리고 데이터베이스 연동을 담당합니다.

5.1 게시글 등록 로직

게시글 등록 로직은 PostRequest를 PostDTO로 변환한 후 데이터베이스에 저장하는 과정을 거칩니다.

@Override
@Transactional
public PostDTO register(Long userId, PostRequest postRequest) {
    validatePostRequest(postRequest);
    PostDTO postDTO = PostDTO.fromRequest(userId, postRequest);
    postMapper.register(postDTO);
    processTagsForPost(postDTO); // 태그 처리 로직
    return postDTO;
}
  • 데이터 검증: 게시글 요청 데이터가 유효한지 확인합니다.
  • 태그 처리: 게시글 등록 시 해당 게시글에 포함된 태그도 처리됩니다.

5.2 댓글 등록 로직

댓글 등록 로직은 CommentRequest를 CommentDTO로 변환하여 저장하는 형태로 구현됩니다.

@Override
public CommentDTO registerComment(Long userId, CommentRequest commentRequest) {
    CommentDTO commentDto = CommentDTO.fromRequest(userId, commentRequest);
    commentMapper.register(commentDto);
    return commentDto;
}
  • 검증 및 등록: 댓글 데이터가 유효한지 확인 후, 데이터베이스에 저장됩니다.

5.3 태그 처리 로직

태그는 게시글 등록 시 함께 처리되며, 태그가 이미 존재하는지 확인한 후 새로운 태그가 없을 경우 생성합니다.

@Override
public void registerTag(Long userId, TagRequest tagRequest) {
    TagDTO existingTag = tagMapper.findTagByName(tagRequest.getName());
    if (existingTag == null) {
        TagDTO tagDTO = TagDTO.fromRequestForCreate(tagRequest);
        tagMapper.register(tagDTO);
    } else {
        log.info("Tag with name {} already exists", tagRequest.getName());
    }
}
  • 태그 존재 여부 확인: 태그가 이미 존재하는지 검사하고, 없으면 새로 등록합니다.

6. 데이터베이스 매퍼: MyBatis 연동

MyBatis는 데이터베이스와의 상호작용을 위해 매퍼 인터페이스를 사용합니다. 각 매퍼는 필요한 쿼리를 정의하고, 이를 통해 데이터베이스와 통신합니다.

6.1 게시글 등록 쿼리

<insert id="register" parameterType="com

.example.boardserver.dto.PostDTO">
    <selectKey keyProperty="id" resultType="Long" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>

    INSERT INTO post(name, isAdmin, contents, createTime, views, categoryId, userId)
    VALUES (#{name}, #{isAdmin}, #{contents}, #{createTime}, #{views}, #{categoryId}, #{userId})
</insert>

이 쿼리는 게시글을 데이터베이스에 등록한 후, 생성된 게시글 ID를 반환합니다.

6.2 게시글 수정 쿼리

<update id="updatePost" parameterType="com.example.boardserver.dto.PostDTO">
    UPDATE post
    SET name = #{name},
        contents = #{contents},
        views = #{views},
        categoryId = #{categoryId},
        userId = #{userId}
    WHERE id = #{id}
</update>

게시글 수정 시 입력된 제목, 내용, 카테고리 등의 데이터를 수정합니다. 각 필드는 #{}로 바인딩되어 요청된 데이터를 업데이트합니다.

6.3 게시글 삭제 쿼리

<delete id="deletePost">
    DELETE FROM post
    WHERE id = #{postId}
</delete>

이 쿼리는 해당 게시글 ID를 사용하여 게시글을 삭제합니다. 삭제되기 전에 게시글과 연결된 댓글 및 태그가 먼저 삭제됩니다.

6.4 댓글 및 태그 삭제 쿼리

<!-- 댓글 삭제 -->
<delete id="deleteCommentsByPostId">
    DELETE FROM comment
    WHERE postId = #{postId}
</delete>

<!-- 태그 삭제 -->
<delete id="deletePostTagByPostId">
    DELETE FROM posttag
    WHERE postId = #{postId}
</delete>
  • 댓글 삭제: 게시글 삭제 시 해당 게시글에 달린 모든 댓글을 삭제합니다.
  • 태그 삭제: 게시글과 연결된 태그를 posttag 테이블에서 삭제합니다.

6.5 태그 및 댓글 관련 쿼리

<!-- TagMapper -->
<insert id="register" parameterType="com.example.boardserver.dto.TagDTO">
    <selectKey keyProperty="id" resultType="Long" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>

    INSERT INTO tag(name, url)
    VALUES (#{name}, #{url})
</insert>

<!-- CommentMapper -->
<insert id="register" parameterType="com.example.boardserver.dto.CommentDTO">
    <selectKey keyProperty="id" resultType="Long" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>

    INSERT INTO comment(postId, contents, subCommentId)
    VALUES (#{postId}, #{contents}, #{subCommentId})
</insert>

각 매퍼는 태그 및 댓글을 등록하기 위한 쿼리를 정의하고, ID 생성 후 데이터를 저장합니다.


결론

이번 시리즈에서 우리는 게시글 CRUD, 댓글 기능, 태그 관리 기능을 성공적으로 구현했습니다. 각 기능은 잘 분리된 구조를 통해 확장 가능하며, MyBatis와의 연동을 통해 데이터베이스와 효과적으로 상호작용할 수 있도록 설계되었습니다. 앞으로도 더 많은 기능을 추가하면서, 대규모 트래픽을 처리할 수 있는 게시판을 완성해 나가겠습니다.

저작자표시 (새창열림)

'프레임워크 > 자바 스프링' 카테고리의 다른 글

대규모 트래픽 게시판 구축 시리즈 #11: 로깅, 예외처리  (0) 2024.09.07
대규모 트래픽 게시판 구축 시리즈 #10: 게시판 검색 API  (0) 2024.09.07
대규모 트래픽 게시판 구축 시리즈 #8: 카테고리 API  (1) 2024.09.07
대규모 트래픽 게시판 구축 시리즈 #7: Spring AOP를 활용한 인증 및 인가  (0) 2024.09.05
대규모 트래픽 게시판 구축 시리즈 #6: 유저 API  (1) 2024.09.05
'프레임워크/자바 스프링' 카테고리의 다른 글
  • 대규모 트래픽 게시판 구축 시리즈 #11: 로깅, 예외처리
  • 대규모 트래픽 게시판 구축 시리즈 #10: 게시판 검색 API
  • 대규모 트래픽 게시판 구축 시리즈 #8: 카테고리 API
  • 대규모 트래픽 게시판 구축 시리즈 #7: Spring AOP를 활용한 인증 및 인가
hyeseong-dev
hyeseong-dev
안녕하세요. 백엔드 개발자 이혜성입니다.
  • hyeseong-dev
    어제 오늘 그리고 내일
    hyeseong-dev
  • 전체
    오늘
    어제
    • 분류 전체보기 (283)
      • 여러가지 (107)
        • 알고리즘 & 자료구조 (72)
        • 오류 (4)
        • 이것저것 (29)
        • 일기 (1)
      • 프레임워크 (39)
        • 자바 스프링 (39)
        • React Native (0)
      • 프로그래밍 언어 (38)
        • 파이썬 (30)
        • 자바 (3)
        • 스프링부트 (5)
      • 운영체제 (0)
      • DB (17)
        • SQL (0)
        • Redis (17)
      • 클라우드 컴퓨팅 (2)
        • 도커 (2)
        • AWS (0)
      • 스케쥴 (65)
        • 세미나 (0)
        • 수료 (0)
        • 스터디 (24)
        • 시험 (41)
      • 트러블슈팅 (1)
      • 자격증 (0)
        • 정보처리기사 (0)
      • 재태크 (5)
        • 암호화폐 (5)
        • 기타 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    EC2
    spring
    reactor
    프로그래머스
    ecs
    Python
    Redis
    FastAPI
    Spring Boot
    docker
    OOP
    Docker-compose
    java
    시험
    취업리부트
    자바
    SAA
    #개발자포트폴리오 #개발자이력서 #개발자취업 #개발자취준 #코딩테스트 #항해99 #취리코 #취업리부트코스
    DP
    RDS
    항해99
    WebFlux
    파이썬
    mybatis
    celery
    백준
    그리디
    Spring WebFlux
    AWS
    완전탐색
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
hyeseong-dev
대규모 트래픽 게시판 구축 시리즈 #9: 게시판 API
상단으로

티스토리툴바