오늘한일
- 과제 맡은 기능 구현하기
- 관리자 로그인과 댓글 관련 기능들 구현 및 수정
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 |