diff --git a/src/main/java/io/company/localhost/common/exception/GlobalExceptionHandler.java b/src/main/java/io/company/localhost/common/exception/GlobalExceptionHandler.java index 37914a2..e001992 100644 --- a/src/main/java/io/company/localhost/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/io/company/localhost/common/exception/GlobalExceptionHandler.java @@ -10,6 +10,7 @@ * DATE AUTHOR NOTE * ----------------------------------------------------------- * 24.12.06 조인제 최초 생성 + * 25.02.10 서지희 InvalidPasswordException추가 * *************************************************************/ package io.company.localhost.common.exception; @@ -28,26 +29,52 @@ import lombok.extern.slf4j.Slf4j; @RestControllerAdvice(annotations = RestController.class) public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - @ExceptionHandler(RestApiException.class) - public ResponseEntity handleCustomException(RestApiException e) { - ErrorCode errorCode = e.getErrorCode(); - return ErrorResult.handleExceptionInternal(errorCode); - } + /** + * 사용자 정의 API 예외 처리 (RestApiException) + */ + @ExceptionHandler(RestApiException.class) + public ResponseEntity handleCustomException(RestApiException e) { + ErrorCode errorCode = e.getErrorCode(); + return ErrorResult.handleExceptionInternal(errorCode); + } - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { - log.warn("handleIllegalArgument", e); - ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; - return ErrorResult.handleExceptionInternal(errorCode, e.getMessage()); - } + /** + * 잘못된 요청 (IllegalArgumentException) 처리 + */ + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + log.warn("handleIllegalArgument", e); + ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; + return ErrorResult.handleExceptionInternal(errorCode, e.getMessage()); + } + /** + * 게시물을 찾을 수 없는 경우 (NotFoundHandler) + */ + @ExceptionHandler(NotFoundHandler.class) + public ResponseEntity handleBoardNotFoundException(NotFoundHandler e) { + log.warn("handleBoardNotFoundException: {}", e.getMessage()); + ErrorCode errorCode = CommonErrorCode.RESOURCE_NOT_FOUND; + return ErrorResult.handleExceptionInternal(errorCode, e.getMessage()); + } - @ExceptionHandler({Exception.class}) - public ResponseEntity handleAllException(Exception ex) { - log.warn("handleAllException", ex); - ErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR; - return ErrorResult.handleExceptionInternal(errorCode); - } + /** + * 비밀번호가 틀린 경우 예외 처리 (InvalidPasswordException) + */ + @ExceptionHandler(InvalidPasswordException.class) + public ResponseEntity handleInvalidPasswordException(InvalidPasswordException e) { + log.warn("handleInvalidPasswordException: {}", e.getMessage()); + ErrorCode errorCode = CommonErrorCode.UNAUTHORIZED; + return ErrorResult.handleExceptionInternal(errorCode, e.getMessage()); + } - -} + /** + * 서버 내부 오류 처리 (Exception) + */ + @ExceptionHandler({Exception.class}) + public ResponseEntity handleAllException(Exception ex) { + log.warn("handleAllException", ex); + ErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR; + return ErrorResult.handleExceptionInternal(errorCode); + } +} \ No newline at end of file diff --git a/src/main/java/io/company/localhost/common/exception/InvalidPasswordException.java b/src/main/java/io/company/localhost/common/exception/InvalidPasswordException.java new file mode 100644 index 0000000..e723a99 --- /dev/null +++ b/src/main/java/io/company/localhost/common/exception/InvalidPasswordException.java @@ -0,0 +1,7 @@ +package io.company.localhost.common.exception; + +public class InvalidPasswordException extends RuntimeException { + public InvalidPasswordException(String message) { + super(message); + } +} diff --git a/src/main/java/io/company/localhost/common/exception/NotFoundHandler.java b/src/main/java/io/company/localhost/common/exception/NotFoundHandler.java index af18be4..118df5c 100644 --- a/src/main/java/io/company/localhost/common/exception/NotFoundHandler.java +++ b/src/main/java/io/company/localhost/common/exception/NotFoundHandler.java @@ -25,15 +25,8 @@ import io.company.localhost.common.exception.code.CommonErrorCode; import io.company.localhost.common.exception.code.ErrorCode; import lombok.extern.slf4j.Slf4j; -@Slf4j -@RestControllerAdvice -public class NotFoundHandler { - - @ResponseStatus(HttpStatus.NOT_FOUND) - @ExceptionHandler(NoHandlerFoundException.class) - public ResponseEntity noHandlerFoundArgument(NoHandlerFoundException e) { - log.warn("NoHandlerFoundException", e); - ErrorCode errorCode = CommonErrorCode.RESOURCE_NOT_FOUND; - return ErrorResult.handleExceptionInternal(errorCode, errorCode.getMessage()); - } +public class NotFoundHandler extends RuntimeException { + public NotFoundHandler(String message) { + super(message); + } } diff --git a/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java b/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java index 5d99fd4..caea7af 100644 --- a/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java +++ b/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java @@ -10,6 +10,7 @@ * DATE AUTHOR NOTE * ----------------------------------------------------------- * 24.12.06 조인제 최초 생성 + * 24.02.10 서지희 비밀번호 불일치 시 권한없음 추가 * *************************************************************/ package io.company.localhost.common.exception.code; @@ -26,6 +27,7 @@ public enum CommonErrorCode implements ErrorCode { INVALID_PARAMETER(HttpStatus.BAD_REQUEST,"잘못된 매개변수가 포함되었습니다."), RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND,"리소스가 존재하지 않습니다"), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"내부 서버 오류"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"권한없음"), ; private final long code; diff --git a/src/main/java/io/company/localhost/controller/api/BoardController.java b/src/main/java/io/company/localhost/controller/api/BoardController.java index f5da9b3..ba4fe49 100644 --- a/src/main/java/io/company/localhost/controller/api/BoardController.java +++ b/src/main/java/io/company/localhost/controller/api/BoardController.java @@ -18,6 +18,7 @@ package io.company.localhost.controller.api; import java.math.BigInteger; import java.util.List; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import com.github.pagehelper.PageInfo; @@ -27,6 +28,8 @@ import io.company.localhost.common.annotation.ParameterCheck; import io.company.localhost.common.annotation.ReqMap; import io.company.localhost.common.dto.ApiResponse; import io.company.localhost.common.dto.MapDto; +import io.company.localhost.common.exception.InvalidPasswordException; +import io.company.localhost.common.exception.NotFoundHandler; import io.company.localhost.service.commoncodService; import io.company.localhost.service.localbordService; import io.company.localhost.utils.AuthUtil; @@ -41,6 +44,7 @@ public class BoardController { private final localbordService boardService; private final commoncodService commoncodService; + private final PasswordEncoder passwordEncoder; /** * 공지사항 목록 조회 @@ -76,8 +80,15 @@ public class BoardController { @ParameterCheck @PostMapping public ApiResponse createBoard(@ReqMap MapDto map) { - Long userId = AuthUtil.getUser().getId(); - map.put("MEMBERSEQ", userId); + Long userId = AuthUtil.getUser().getId(); + map.put("MEMBERSEQ", 22); + + if (map.containsKey("LOCBRDPWD") && !map.getString("LOCBRDPWD").trim().isEmpty()) { // 빈 값 체크 + String rawPassword = map.getString("LOCBRDPWD"); + String hashedPassword = passwordEncoder.encode(rawPassword); + map.put("LOCBRDPWD", hashedPassword); + } + return ApiResponse.ok(boardService.createBoard(map)); } @@ -90,7 +101,11 @@ public class BoardController { @ParameterCheck @GetMapping("/{boardId}") public ApiResponse getBoardDetail(@PathVariable("boardId") Long boardId) { - return ApiResponse.ok(boardService.getBoardDetail(boardId)); + MapDto board = boardService.getBoardDetail(boardId); + if (board == null) { + throw new NotFoundHandler("게시물 ID " + boardId + "을(를) 찾을 수 없습니다."); + } + return ApiResponse.ok(board); } /** @@ -129,7 +144,7 @@ public class BoardController { @PostMapping("/{CMNBRDSEQ}/attachments") public ApiResponse uploadAttachment(@ReqMap MapDto map) { Long userId = AuthUtil.getUser().getId(); - map.put("CMNFLEREG", userId); + map.put("CMNFLEREG", 22); boardService.addAttachment(map); return ApiResponse.ok("첨부파일이 저장되었습니다."); } @@ -144,7 +159,7 @@ public class BoardController { @PostMapping("/{LOCBRDSEQ}/{LOCCMTSEQ}/reaction") public ApiResponse reactToBoard(@ReqMap MapDto map) { Long userId = AuthUtil.getUser().getId(); - map.put("MEMBERSEQ", userId); + map.put("MEMBERSEQ", 22); boardService.reactToBoard(map); return ApiResponse.ok("반응이 성공적으로 처리되었습니다."); } @@ -172,7 +187,13 @@ public class BoardController { @PostMapping("/{LOCBRDSEQ}/comment") public ApiResponse addCommentOrReply(@ReqMap MapDto map) { Long userId = AuthUtil.getUser().getId(); - map.put("MEMBERSEQ", userId); + map.put("MEMBERSEQ", 22); + + if (map.containsKey("LOCCMTPWD") && !map.getString("LOCCMTPWD").trim().isEmpty()) { // 빈 값 체크 + String rawPassword = map.getString("LOCCMTPWD"); + String hashedPassword = passwordEncoder.encode(rawPassword); + map.put("LOCCMTPWD", hashedPassword); + } boardService.addCommentOrReply(map); return ApiResponse.ok("댓글 또는 대댓글이 작성되었습니다."); } @@ -205,20 +226,7 @@ public class BoardController { } /** - * 댓글 비밀번호 확인 - * @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTPWD) - * @return 비밀번호 확인 결과 - */ - @Member - @ParameterCheck - @PostMapping("/comment/{commentId}/password") - public ApiResponse checkCommentPassword(@ReqMap MapDto map) { - int commentId = (int) map.get("LOCCMTPWD"); - return ApiResponse.ok(boardService.getCommentPassword(commentId).equals(map.getString("LOCCMTPWD"))); - } - - /** - * 게시물 비밀번호 확인 + * 게시물 비밀번호 확인 (해싱된 비밀번호 비교 적용) * @ReqMap map 수정 데이터 (LOCBRDSEQ, LOCBRDPWD) * @return 비밀번호 확인 결과 */ @@ -226,8 +234,45 @@ public class BoardController { @ParameterCheck @PostMapping("/{boardId}/password") public ApiResponse checkBoardPassword(@ReqMap MapDto map) { - int boardId = (int) map.get("LOCBRDSEQ"); - return ApiResponse.ok(boardService.getBoardPassword(boardId).equals(map.getString("LOCBRDPWD"))); + int boardId = (int) map.get("LOCBRDSEQ"); + String rawPassword = map.getString("LOCBRDPWD"); + + String storedHashedPassword = boardService.getBoardPassword(boardId); + if (storedHashedPassword == null) { + throw new NotFoundHandler("해당 게시물이 존재하지 않습니다."); + } + + boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword); + if (!isMatch) { + throw new InvalidPasswordException("비밀번호가 일치하지 않습니다."); + } + + return ApiResponse.ok(true); + } + + /** + * 댓글 비밀번호 확인 (해싱된 비밀번호 비교 적용) + * @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTPWD) + * @return 비밀번호 확인 결과 + */ + @Member + @ParameterCheck + @PostMapping("/comment/{commentId}/password") + public ApiResponse checkCommentPassword(@ReqMap MapDto map) { + int commentId = (int) map.get("LOCCMTSEQ"); + String rawPassword = map.getString("LOCCMTPWD"); + + String storedHashedPassword = boardService.getCommentPassword(commentId); + if (storedHashedPassword == null) { + throw new NotFoundHandler("해당 댓글이 존재하지 않습니다."); + } + + boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword); + if (!isMatch) { + throw new InvalidPasswordException("비밀번호가 일치하지 않습니다."); + } + + return ApiResponse.ok(true); } /** diff --git a/src/main/java/io/company/localhost/service/localbordService.java b/src/main/java/io/company/localhost/service/localbordService.java index cddff6d..a5cab39 100644 --- a/src/main/java/io/company/localhost/service/localbordService.java +++ b/src/main/java/io/company/localhost/service/localbordService.java @@ -171,45 +171,24 @@ public class localbordService { return boardMapper.getCommentReactions(boardId); } - private void enrichBoardDetail(MapDto boardDetail) { - long boardId = ((Number) boardDetail.get("id")).longValue(); - boardDetail.put("hasAttachment", hasAttachments(boardId)); - boardDetail.put("commentCount", getCommentCount(boardId)); - MapDto reactions = getBoardReactions(boardId); - boardDetail.put("likeCount", reactions.getOrDefault("likeCount", 0)); - boardDetail.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0)); - - // Blob 데이터를 문자열로 변환 - Object content = boardDetail.get("content"); - if (content != null) { - String contentString = convertBlobToString(content); // Blob을 문자열로 변환 - boardDetail.put("content", contentString); // JSON 변환 가능 - } - } - private String convertBlobToString(Object blob) { try { if (blob instanceof String) { - return (String) blob; // 이미 문자열인 경우 반환 + return (String) blob; // 이미 문자열이면 그대로 반환 } else if (blob instanceof java.sql.Blob) { java.sql.Blob sqlBlob = (java.sql.Blob) blob; long blobLength = sqlBlob.length(); byte[] blobBytes = sqlBlob.getBytes(1, (int) blobLength); - return new String(blobBytes, StandardCharsets.UTF_8); - } else if (blob instanceof ByteArrayInputStream) { - ByteArrayInputStream inputStream = (ByteArrayInputStream) blob; - byte[] bytes = inputStream.readAllBytes(); - return new String(bytes, StandardCharsets.UTF_8); + return new String(blobBytes, StandardCharsets.UTF_8); // SQL BLOB → 바이트 배열 → 문자열 변환 } else { - System.err.println("Unsupported blob type: " + blob.getClass()); + throw new UnsupportedOperationException("Unsupported blob type: " + blob.getClass()); // 지원되지 않는 타입이면 예외 발생 } } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Failed to convert Blob to String: " + e.getMessage(), e); + throw new RuntimeException("Failed to convert Blob to String: " + e.getMessage(), e); // 변환 실패 시 예외 처리 } - return null; } + private String extractFirstImageUrl(String jsonContent) { try { // JSON 유효성 검사 @@ -276,7 +255,23 @@ public class localbordService { } return plainTextBuilder.toString(); } + + private void enrichBoardDetail(MapDto boardDetail) { + long boardId = ((Number) boardDetail.get("id")).longValue(); + boardDetail.put("hasAttachment", hasAttachments(boardId)); + boardDetail.put("commentCount", getCommentCount(boardId)); + MapDto reactions = getBoardReactions(boardId); + boardDetail.put("likeCount", reactions.getOrDefault("likeCount", 0)); + boardDetail.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0)); + // Blob 데이터를 문자열로 변환 + Object content = boardDetail.get("content"); + if (content != null) { + String contentString = convertBlobToString(content); // Blob을 문자열로 변환 + boardDetail.put("content", contentString); // JSON 변환 가능 + } + } + private void enrichPostsWithAdditionalData(List posts) { for (MapDto post : posts) { Object idObject = post.get("id");