슬기의 TIL - 2023.04.21
어제 구현한 lv3과제를 요구사항에 맞춰 하나씩 기능 확인을 하고, 수정이 필요한 부분들이 있어서 수정해 제출했다.
정규표현식
비밀번호 입력할 때 !가 작동을 안 하는 것 같아서 코드를 다시 들여다봤다.
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,15}$",
message = "비밀번호는 8~15자 알파벳 대소문자, 숫자, 특수문자로 작성해주세요.")
(?=.*[@#$%^&+=]) 이렇게 작성했는데, [ ] 안의 특수문자들을 입력해도 된다는 뜻이었다.
내가 아는 특수문자들을 다 나열해 주면 되는 건가? 싶어서 정규표현식 특수문자 유효성 검사에 대해 조금 더 찾아봤다.
두 가지 방법을 찾을 수 있었다.
?=.*[~!@#$%^&*()_+|<>?:{}]
이건 특수문자를 직접 입력해 준 것이고,
?=.*\W+
이건 [A-z0-9]를 제외한 문자열(공백, __를 제외한 특수문자 등)만 선택하는 것이다.
\W를 사용하면 코드가 더 깔끔해질 것 같아서 공식문서를 찾아봤다.
Matches any character that is not a word character from the basic Latin alphabet. Equivalent to [^A-Za-z0-9_]. For example, /\W/ or /[^A-Za-z0-9_]/ matches "%" in "50%" and "É" in "Émanuel".
예시를 보니 확 와닿았다.
비밀번호에 "É" 와 같은 특수문자를 받고 싶은 건 아니니, 특수문자를 직접 지정해 주는 첫 번째 방식을 적용하기로 했다.
추가로 알게 된 것도 있었다.
숫자는 [0-9]로도 표현하지만 \\d로도 표현한다. 그래서 바꿔주었다.
예외처리
요구사항을 보면 예외 발생 시 메시지와 상태코드 400을 클라이언트에 반환하라고 되어있다.
동일한 로직으로 수행되는 작업들이라 메시지와 코드를 하나의 객체로 묶어 사용하고 싶었다.
에러 코드들을 ErrorCode라는 클래스로 묶어 사용하고, 형식은 Enum클래스로 정의했다.
응답으로 보낼 HttpStatus와 메시지인 String도 가지고 있도록 했다.
@Getter
@AllArgsConstructor
public enum ErrorCode {
POST_NOT_FOUND(BAD_REQUEST, "게시글이 존재하지 않습니다."),
COMMENT_NOT_FOUND(BAD_REQUEST, "해당 댓글이 존재하지 않습니다."),
CANNOT_FOUND_USERNAME(BAD_REQUEST, "사용자가 존재하지 않습니다."),
AUTHOR_NOT_SAME_MOD(BAD_REQUEST, "작성자만 수정할 수 있습니다."),
AUTHOR_NOT_SAME_DEL(BAD_REQUEST, "작성자만 삭제할 수 있습니다."),
INVALIDATED_TOKEN(BAD_REQUEST, "토큰이 유효하지 않습니다."),
NOT_MATCH_ADMIN_TOKEN(BAD_REQUEST, "관리자 암호가 틀려 등록이 불가능합니다."),
CANNOT_FOUND_USER(BAD_REQUEST, "회원을 찾을 수 없습니다."),
EXIST_USERNAME(BAD_REQUEST, "중복된 username 입니다.");
private final HttpStatus httpStatus;
private final String detail;
}
예외처리를 전역으로 하기 위해 CustomException 클래스도 만들었다.
RuntimeException을 상속받아서 컴파일러가 체크하지 않는 실행 예외로 선언했다.
@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
}
그리고 클라이언트에게 보낼 응답 형식을 위한 클래스도 만들어 주었다.
요구사항처럼 메시지와 상태코드가 반환되도록 해주었다.
@Getter
@Builder
public class ErrorResponse {
private final String message;
private final int status;
public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
return ResponseEntity
.status(errorCode.getHttpStatus())
.body(ErrorResponse.builder()
.message(errorCode.getDetail())
.status(errorCode.getHttpStatus().value())
.build()
);
}
}
그리고 중요한 GlobalExceptionHandler
발생한 예외를 잡아서 처리해 줄 수 있는 핸들러를 추가했다.
나는 throw로 에러를 던져줬는데, try-catch문을 써준 게 아니라 잡아주는 곳이 필요했기 때문에 핸들러를 사용했다.
CustomException클래스에서 예외 발생 시 넘어오는 ErrorCode를 ErrorResponse 형식으로 클라이언트에게 보내주었다.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
return ErrorResponse.toResponseEntity(e.getErrorCode());
}
}
사용할 때는 이렇게 하면 된다!
// 댓글 DB 저장 유무 확인
Comment comment = commentRepository.findById(id).orElseThrow(
() -> new CustomException(COMMENT_NOT_FOUND)
);
앞으로 프로젝트를 하다 보면 예외처리 할 일이 많을 텐데, 여유가 있을 때 try-catch문도 사용해 코드를 수정해 봐야겠다.
그리고!
분명 작성자인데 작성자가 아니라고 수정, 삭제가 안 되는 일이 있었다.
// 기존 코드
// 게시글이 존재하는지 확인
Post post = postRepository.findById(id).orElseThrow(
() -> new CustomException(POST_NOT_FOUND)
);
UserRoleEnum userRoleEnum = user.getRole();
// 관리자인지 확인하고, 관리자면 게시글 수정
if(userRoleEnum == UserRoleEnum.ADMIN) {
post.update(requestDto);
return new PostResponseDto(post, getCommentList(id));
} else { // 아니면 작성자인지 확인하고 게시글 수정
if(post.getUsername() != user.getUsername()) {
throw new CustomException(AUTHOR_NOT_SAME_MOD);
}
post.update(requestDto);
return new PostResponseDto(post, getCommentList(id));
}
}
처음에 게시글이 있는지 확인 -> 관리자 여부 확인 -> 관리자면 바로 수정, 아니면 작성자인지 확인 -> 맞으면 수정 로직으로 생각해서 구현했었다.
작성자가 맞는데 안되길래.. 순서를 바꿔봤다.
관리자 여부 확인 -> 맞으면 게시글이 있는지 확인하고, 아니면 작성자인지 확인하고 수정
Post post;
if(userRoleEnum == UserRoleEnum.ADMIN) {
post = postRepository.findById(id).orElseThrow(
() -> new CustomException(POST_NOT_FOUND)
);
} else {
post = postRepository.findByIdAndUsername(id, user.getUsername()).orElseThrow(
() -> new CustomException(AUTHOR_NOT_SAME_MOD)
);
}
post.update(requestDto);
return new PostResponseDto(post, getCommentList(id));
}
바꿨더니 잘 동작한다.
같은 로직으로 게시글 수정/삭제, 댓글 수정/삭제 모두 바꿔서 완성했다.
근데 왜 기존 코드는 잘못된 건지 모르겠다. 질문드려봐야지!
+ 나는 멍청이..!!!!!!!!!!
알고리즘 문제 풀 때도 그렇고, 자꾸 String 비교를 == 으로 시도한다.......
문자열 비교는 equals를 써야 한다!
참고
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes
https://bcp0109.tistory.com/303