본문 바로가기

TIL

231208_TIL

오늘한일

  • 과제 맡은 기능 구현하기 
  • 관리자 로그인과 댓글 관련 기능들 구현 및 수정

 

Controller

  • 댓글과 관련된 API를 처리하는 Spring Boot 컨트롤러입니다.
  • 댓글 생성, 조회, 수정, 삭제에 대한 엔드포인트가 정의되어 있습니다
// 컨트롤러: 댓글 관련 API를 처리하는 컨트롤러 클래스

@RestController
@RequestMapping("/api/posts/{postId}/comments")
@RequiredArgsConstructor
public class CommentController {

    private final CommentService commentService;


//    댓글 생성 API
//    requestDto 댓글 생성 요청 DTO
//    userDetail 현재 인증된 사용자의 UserDetails 객체
//    HTTP 응답: 성공 시 메시지

    @PostMapping
    public ResponseEntity<String> createComment(@PathVariable Long postId,
                                                @RequestBody CommentRequestDto requestDto,
                                                @AuthenticationPrincipal UserDetailsImpl userDetail) {
        commentService.createComment(postId,requestDto, userDetail.getUser());
        return ResponseEntity.ok(CommonCode.OK.getMessage());
    }


//     모든 댓글 조회 API
//     userDetail 현재 인증된 사용자의 UserDetails 객체
//     모든 댓글에 대한 응답 DTO 리스트
//
//    @GetMapping
//    public List<CommentResponseDto> getComments(@AuthenticationPrincipal UserDetailsImpl userDetail) {
//        return commentService.getComments(userDetail.getUser());
//    }

//     댓글 수정 API
//     commentId 수정할 댓글의 식별자
//     requestDto 수정할 내용을 담은 DTO
//     userDetail 현재 인증된 사용자의 UserDetails 객체
//     HTTP 응답: 성공 시 메시지

    @PutMapping("/{commentId}")
    public ResponseEntity<String> updateComment(@PathVariable Long postId,
                                                @PathVariable Long commentId,
                                                @RequestBody CommentRequestDto requestDto,
                                                @AuthenticationPrincipal UserDetailsImpl userDetail) {
        commentService.updateComment(postId,commentId, requestDto, userDetail.getUser());
        return ResponseEntity.ok(CommonCode.OK.getMessage());
    }

    //     댓글 삭제 API
//     commentId 삭제할 댓글의 식별자
//     userDetail 현재 인증된 사용자의 UserDetails 객체
//     HTTP 응답: 성공 시 메시지
    @DeleteMapping("/{commentId}")
    public ResponseEntity<String> deleteComment(@PathVariable Long postId,
                                                @PathVariable Long commentId,
                                                @AuthenticationPrincipal UserDetailsImpl userDetail) {
        commentService.deleteComment(postId,commentId, userDetail.getUser());
        return ResponseEntity.ok(CommonCode.OK.getMessage());
    }
}

 

CommentRequestDto

  • CommentRequestDto: 댓글 생성 시 필요한 데이터를 받기 위한 DTO입니다.
package com.example.backoffice.domain.comment.dto;

import lombok.Getter;

@Getter
public class CommentRequestDto {
    private String texts;              // 댓글 내용
}

 

CommentResponseDto

  • CommentResponseDto: 댓글 조회 시 클라이언트에 반환할 데이터를 담은 DTO입니다.
package com.example.backoffice.domain.comment.dto;

import com.example.backoffice.domain.comment.entity.Comment;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class CommentResponseDto {
    private Long postId;
    private Long commentId;                     // 댓글 ID
    private String mbti;                 //mbti
    private Long commentLikeCount;     // 댓글 좋아요 수
    private String texts;             // 댓글 내용
    private LocalDateTime createdAt;    // 댓글 생성일시

    // Comment 엔티티를 기반으로 하는 CommentResponseDto 생성자
    public CommentResponseDto(Comment comment) {
        this.postId= comment.getPost().getId();
        this.commentId = comment.getId();
        this.mbti = comment.getUser().getMbti();
        this.commentLikeCount = comment.getCommentLikeCount();
        this.texts = comment.getTexts();
        this.createdAt = comment.getCreatedAt();
    }
}

 

Entity (Comment)

  • 댓글 정보를 나타내는 JPA 엔티티입니다.
  • 댓글 생성, 수정, 삭제에 관한 핵심 로직이 구현되어 있습니다.
package com.example.backoffice.domain.comment.entity;

import com.example.backoffice.domain.comment.dto.CommentRequestDto;
import com.example.backoffice.domain.commentLike.entity.Likes;
import com.example.backoffice.domain.post.entity.Post;
import com.example.backoffice.domain.utils.BaseTime;
import com.example.backoffice.domain.user.entity.User;
import jakarta.persistence.*;
import lombok.*;

import java.util.ArrayList;
import java.util.List;


//엔티티 클래스: 댓글 정보를 나타내는 엔티티

@Entity
@Table(name = "comment")
@Getter
@NoArgsConstructor
public class Comment extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;


    private Long commentLikeCount;

    @Column(name = "texts", nullable = false, length = 500)
    private String texts;

    @OneToMany(mappedBy = "comment", cascade = CascadeType.REMOVE)
    List<Likes> likesList = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;


    //    엔티티의 빌더 클래스
//    id 댓글 식별자
//    texts 댓글 내용
//    user 작성자 정보
//    post 댓글이 달린 게시물 정보
    @Builder
    private Comment(Long id, String texts, User user, Post post) {
        this.commentLikeCount = 0L;
        this.id = id;
        this.texts = texts;
        this.user = user;
        this.post = post;
    }


//     댓글 내용을 업데이트하는 메소드
//     requestDto 댓글 수정 요청 DTO

    public void update(CommentRequestDto requestDto) {
        this.texts = "(수정됨)"+requestDto.getTexts();
    }


//    댓글 좋아요 수를 업데이트하는 메소드
//    updated 업데이트 여부를 나타내는 값 (true: 증가, false: 감소)

    public void updateLikeCount(Boolean updated) {
        if (updated) {
            this.commentLikeCount++;
        } else {
            this.commentLikeCount--;
        }
    }
}

 

예외처리 

  • 댓글과 관련된 예외 처리를 위한 클래스들이 정의되어 있습니다.
  • 예외 코드 및 메시지, HTTP 상태코드 등이 명시되어 있습니다.
package com.example.backoffice.domain.comment.exception;


import com.example.backoffice.global.exception.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum CommentErrorCode implements ErrorCode {
    UNAUTHORIZED_USER(HttpStatus.FORBIDDEN, "수정 권한이 없습니다."),
    NO_COMMENT(HttpStatus.NOT_FOUND, "존재하지 않는 댓글입니다.");

    private final HttpStatus httpStatus;
    private final String message;
}
package com.example.backoffice.domain.comment.exception;


import com.example.backoffice.global.exception.ErrorCode;
import com.example.backoffice.global.exception.RestApiException;

public class CommentExistsException extends RestApiException {

    public CommentExistsException(ErrorCode errorCode) {
        super(errorCode);
    }
}

 

 

Service

  • 비즈니스 로직을 처리하는 서비스 클래스입니다.
  • 댓글 생성, 조회, 수정, 삭제에 대한 핵심 로직이 정의되어 있습니다.
package com.example.backoffice.domain.comment.service;

import com.example.backoffice.domain.comment.dto.CommentRequestDto;
import com.example.backoffice.domain.comment.dto.CommentResponseDto;
import com.example.backoffice.domain.comment.entity.Comment;
import com.example.backoffice.domain.comment.exception.CommentErrorCode;
import com.example.backoffice.domain.comment.exception.CommentExistsException;
import com.example.backoffice.domain.comment.repository.CommentRepository;
import com.example.backoffice.domain.post.entity.Post;
import com.example.backoffice.domain.post.exception.PostErrorCode;
import com.example.backoffice.domain.post.exception.PostExistException;
import com.example.backoffice.domain.post.repository.PostRepository;
import com.example.backoffice.domain.user.entity.User;
import com.example.backoffice.domain.user.entity.UserRoleEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

//댓글 관련 비즈니스 로직을 처리하는 서비스 클래스

@Service
@RequiredArgsConstructor
public class CommentService {
    private  final PostRepository postRepository;
    private final CommentRepository commentRepository;
//   댓글 생성 서비스
//   requestDto 생성할 댓글 정보를 담은 DTO
//   user 작성자 정보
//   생성된 댓글 정보를 담은 DTO

    public CommentResponseDto createComment(Long postId, CommentRequestDto requestDto, User user) {
        Post post = findById(postId);
        Comment comment = Comment.builder()
                .texts(requestDto.getTexts())
                .post(post)
                .user(user)
                .build();

        // DB 저장
        Comment savedComment = commentRepository.save(comment);

        // Entity -> ResponseDto
        CommentResponseDto commentResponseDto = new CommentResponseDto(savedComment);

        return commentResponseDto;
    }


//     모든 댓글 조회 서비스 (좋아요 수 기준 내림차순 정렬)
//     user 현재 로그인한 사용자 정보
//     조회된 댓글 정보 목록을 담은 DTO 리스트

//    public List<CommentResponseDto> getComments(User user) {
//
//        // DB 조회
//        return commentRepository.findAllByOrderByCommentLikeCountDesc().stream()
//                .map(CommentResponseDto::new)
//                .toList();
//    }


//    댓글 업데이트 서비스
//    commentId 업데이트할 댓글의 식별자
//    requestDto 업데이트할 댓글 정보를 담은 DTO
//    user 현재 로그인한 사용자 정보
//    업데이트된 댓글의 식별자

    @Transactional
    public Long updateComment(Long postId,Long commentId, CommentRequestDto requestDto, User user) {
        Comment comment = findComment(commentId);
        Post post = findById(postId);
        checkAuthorization(comment, user);

        // 댓글 내용 수정
        comment.update(requestDto);

        return commentId;
    }

//     댓글 삭제 서비스
//     commentId 삭제할 댓글의 식별자
//     user 현재 로그인한 사용자 정보
//     삭제된 댓글의 식별자

    public Long deleteComment(Long postId,Long commentId, User user) {
        // 해당 댓글이 DB에 존재하는지 확인
        Comment comment = findComment(commentId);
        Post post = findById(postId);
        checkAuthorization(comment, user);

        // 댓글 삭제
        commentRepository.delete(comment);

        return commentId;
    }


//   commentId 조회할 댓글의 식별자
//   조회된 댓글 엔티티

    public Comment findComment(Long commentId) {
        return commentRepository.findById(commentId)
                .orElseThrow(() -> new CommentExistsException(CommentErrorCode.NO_COMMENT));
    }

//    댓글 작성자 권한 확인 메소드
//    comment 확인할 댓글 엔티티
//    user 현재 로그인한 사용자 정보
//    CommentExistsException 작성자 권한이 없을 경우 발생하는 예외

    private void checkAuthorization(Comment comment, User user) {


        // 객체 동등성 비교
        if (!Objects.equals(comment.getUser().getId(), user.getId())&&user.getRole().equals(UserRoleEnum.USER)) {
            throw new CommentExistsException(CommentErrorCode.UNAUTHORIZED_USER);
        }
    }

    private Post findById(Long postId) {
        return postRepository.findById(postId).orElseThrow(
                () -> new PostExistException(PostErrorCode.NO_POST));
    }
}

 

 

관리자 컨트롤러

  • 관리자 기능과 관련된 API를 처리하는 컨트롤러가 구현되어 있습니다.
  • 회원가입, 유저 페이지 확인, 회원 삭제에 대한 엔드포인트가 정의되어 있습니다.
   
   회원가입
   @PostMapping("/signup")
    public ResponseEntity<String> signup(@Validated @RequestBody SignUpRequestDTO signUpRequestDTO){
        userService.signup(signUpRequestDTO);
        return ResponseEntity.ok(CommonCode.OK.getMessage());
    }
    
        //관리자용 원하는 유저의 마이페이지 확인
    @GetMapping("/{userId}")
    public ResponseEntity<MypageResponseDTO> getUserPage (@PathVariable Long userId,
                                                          @AuthenticationPrincipal UserDetailsImpl userDetails) {
        MypageResponseDTO responseDto = userService.getUserPage(userId,userDetails.getUser());
        return ResponseEntity.ok(responseDto);
    }
    
//관리자용 회원 삭제
    @DeleteMapping("/{userId}")
    public ResponseEntity<String> deleteUser(@PathVariable Long userId,
                                                @AuthenticationPrincipal UserDetailsImpl userDetail) {
        userService.deleteUser(userId, userDetail.getUser());
        return ResponseEntity.ok(CommonCode.OK.getMessage());
    }

 

회원가입 DTO

package com.example.backoffice.domain.user.dto;

import jakarta.validation.constraints.NotBlank;

import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
public class SignUpRequestDTO {

    @NotBlank(message = "아이디는 필수 입력 값입니다.")
    @Pattern(regexp = "^[a-z0-9]{4,10}$", message = "사용자 아이디는 소문자와 숫자로만 이루어져있는 4~10글자로 입력해주세요.")
    String username;

    @NotBlank(message = "비밀번호는 필수 입력 값입니다.")
    @Pattern(regexp = "^[a-zA-Z0-9]{8,15}$", message = "비밀번호는 소문자, 대문자, 숫자로만 이루어진 8~15글자로 입력해주세요.")
    String password;

    @NotBlank(message = "mbti는 필수 입력 값입니다.")
    @Size(min= 4, max= 4, message = "mbti는 4글자 입니다.")
    String mbti;

    @Size(min= 1, max= 15, message = "한줄 소개의 길이는 1~15글자 사이입니다.")
    String intro;

    // 아래 변수는 관리자 토큰을 저장하는 문자열입니다.
    private String adminToken = "";

    // 아래 변수는 관리자 여부를 나타내는 불리언 값입니다.
    private boolean admin = false;


}

 

 

유저 객체

 

package com.example.backoffice.domain.user.entity;

import com.example.backoffice.domain.user.dto.SignUpRequestDTO;

import com.example.backoffice.domain.user.dto.UpdateUserRequestDTO;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.sql.Update;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.List;


@Entity
@Table(name = "users")
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String username;

    @Column
    private String password;

    @Column
    private String mbti;

    @Column(nullable = false)// null 값이 허용되지 않음
    @Enumerated(value = EnumType.STRING)//Enum 상수를 문자열로 저장
    private UserRoleEnum role; // 사용자 역할(UserRoleEnum)


    @Column
    private String intro;

    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
    private List<PasswordHistory> passwordHistoryList;

    @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
    private List<PasswordHistory> delPasswordHistoryList;



    public void updateUser(UpdateUserRequestDTO userRequestDTO){
        this.intro = userRequestDTO.getIntro();
        this.mbti = userRequestDTO.getMbti();
        this.password = userRequestDTO.getPassword();
    }
    public void setPassword(String password){
        this.password = password;
    }
    @Builder
    private User (String username, String password, String mbti, String intro, UserRoleEnum role){
        this.username = username;
        this.password = password;
        this.mbti = mbti;
        this.intro = intro;
        this.role = role;
    }

}

 

 

UserRoleEnum

package com.example.backoffice.domain.user.entity;

// 사용자 역할을 정의하는 Enum 클래스
public enum UserRoleEnum {
    USER(Authority.USER),  // 사용자 권한을 나타내는 Enum 상수, 권한 문자열은 "ROLE_USER"
    ADMIN(Authority.ADMIN);  // 관리자 권한을 나타내는 Enum 상수, 권한 문자열은 "ROLE_ADMIN"

    private final String authority; // Enum 상수에 대한 권한 문자열

    // Enum 생성자, 권한 문자열을 초기화
    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    // 권한 문자열을 반환하는 메서드
    public String getAuthority() {
        return this.authority;
    }

    // 권한 문자열을 정의하는 내부 클래스
    public static class Authority {
        public static final String USER = "ROLE_USER";   // 사용자 권한을 나타내는 문자열
        public static final String ADMIN = "ROLE_ADMIN"; // 관리자 권한을 나타내는 문자열
    }
}

 

User회원가입

@Transactional
    public void signup(SignUpRequestDTO signUpRequestDTO) {
        String username = signUpRequestDTO.getUsername();
        String password = passwordEncoder.encode(signUpRequestDTO.getPassword());
        String mbti = signUpRequestDTO.getMbti();
        String intro = signUpRequestDTO.getIntro();

        // 사용자 ROLE 확인
        UserRoleEnum role = UserRoleEnum.USER; // 기본적으로 사용자로 초기화

        // 만약 회원가입 요청이 관리자인 경우
        if (signUpRequestDTO.isAdmin()) {
            // 만약 관리자 토큰이 기대하는 토큰과 일치하지 않으면 예외를 던짐
            if (!ADMIN_TOKEN.equals(signUpRequestDTO.getAdminToken())) {
                throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
            }
            // 관리자 토큰이 일치하면 사용자 역할을 관리자로 변경
            role = UserRoleEnum.ADMIN;
        }


        Optional<User> userOptional = userRepository.findByUsername(username);
        if (userOptional.isPresent()) {
            throw new AlreadyExistUserException(ALREADY_EXSIST_USER);
        }
        User user = User.builder()
                .username(username)
                .password(password)
                .mbti(mbti)
                .intro(intro)
                .role(role)// 사용자 역할(Role) 설정 (기본값은 USER, 관리자인 경우 ADMIN으로 설정될 수 있음)
                .build();

        PasswordHistory passwordHistory = PasswordHistory.builder().
                password(password)
                .user(user)
                .build();

        // 연관관계 맺었을 때 save 순서가 중요하다. 왜냐하면 passwordHistory 는 user_id를 가져야 하는데
        // user보다 먼저 save되면 user의 id를 몰라서 user_id를 가질 수 없다.
        userRepository.save(user);
        passwordHistoryRepository.save(passwordHistory);
    }

 

 

관리자용 유저 페이지 확인 및  회원탈퇴

  • 사용자 정보, 회원가입과 관련된 클래스 및 메소드들이 있습니다.
  • 비밀번호 히스토리, 사용자 역할, 회원가입 등이 구현되어 있습니다.
  public MypageResponseDTO getUserPage(Long userId, User requestsUser) {
        // 사용자의 역할 확인
        checkUserRole(requestsUser);

        // 특정 사용자 식별자로 사용자 조회
        user = findById(userId);

        // 조회된 사용자 정보를 MypageResponseDTO로 변환하여 반환
        return new MypageResponseDTO(user);
    }



    @Transactional
    public Long deleteUser(Long userId, User requestingUser) {
        // 사용자의 역할 확인
        checkUserRole(requestingUser);

        // 특정 사용자 식별자로 사용자 조회
        User userToDelete = findById(userId);

        // 댓글 삭제
        userRepository.delete(userToDelete);

        return userId;
    }


    // 특정 사용자 식별자로 사용자를 조회하는 메소드.
    //userId 사용자 식별자
    //NonUserExsistException 사용자가 존재하지 않을 경우 발생하는 예외
    private User findById(Long userId) {
        return userRepository.findById(userId).orElseThrow(
                () -> new NonUserExsistException(UserErrorCode.NON_USER));
    }

    //사용자의 역할을 확인하여 특정 권한이 없으면 예외를 발생시키는 메소드.
    //user 확인할 사용자 객체
    //PostExistException 권한이 없을 경우 발생하는 예외\
    private void checkUserRole(User user) {
        if (user.getRole().equals(UserRoleEnum.USER)) {
            throw new PostExistException(PostErrorCode.NO_AUTHORITY);
        }
    }

'TIL' 카테고리의 다른 글

231212_TIL  (0) 2023.12.13
231211_TIL  (0) 2023.12.13
231206_TIL  (0) 2023.12.07
231130_TIL  (0) 2023.12.01
231129_TIL  (0) 2023.11.29