From 0374765dc370768e5fd44d45875c15b54a40f8cc Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 22 Jan 2026 22:42:20 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat(ApiResponseDto):=20name=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20data=20schema=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/zim/tave/memory/global/common/ApiResponseDto.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java b/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java index 5cd4743..a9044ed 100644 --- a/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java +++ b/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java @@ -10,7 +10,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Schema(name = "ApiResponseDto", description = "공통 응답 형식") +@Schema(description = "공통 응답 형식") public class ApiResponseDto { @Schema(description = "응답 코드") @@ -18,6 +18,8 @@ public class ApiResponseDto { @Schema(description = "응답 메시지") private String message; + + @Schema(description = "데이터") private T data; // 성공 응답 From 4be340f3b9f2d6e68df94bb32a5dc27e13ca13a3 Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 22 Jan 2026 22:44:27 +0900 Subject: [PATCH 2/5] feat(OpenApiConfig): add ApiResponseDto schema registration for standardized error responses --- .../zim/tave/memory/config/OpenApiConfig.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/zim/tave/memory/config/OpenApiConfig.java b/src/main/java/zim/tave/memory/config/OpenApiConfig.java index cc2f267..5e4e7cd 100644 --- a/src/main/java/zim/tave/memory/config/OpenApiConfig.java +++ b/src/main/java/zim/tave/memory/config/OpenApiConfig.java @@ -6,6 +6,9 @@ import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.annotations.servers.Server; +import io.swagger.v3.oas.models.media.Schema; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @@ -29,4 +32,33 @@ in = SecuritySchemeIn.HEADER ) public class OpenApiConfig { + + @Bean + public OpenApiCustomizer apiResponseDtoVoidSchemaCustomizer() { + return openApi -> { + // ApiResponseDto 스키마를 명시적으로 등록 + Schema voidSchema = new Schema<>(); + voidSchema.setType("object"); + voidSchema.setDescription("공통 응답 형식 (에러 응답용 - data 필드는 null)"); + + Schema codeSchema = new Schema<>(); + codeSchema.setType("integer"); + codeSchema.setDescription("응답 코드"); + voidSchema.addProperty("code", codeSchema); + + Schema messageSchema = new Schema<>(); + messageSchema.setType("string"); + messageSchema.setDescription("응답 메시지"); + voidSchema.addProperty("message", messageSchema); + + Schema dataSchema = new Schema<>(); + dataSchema.setType("object"); + dataSchema.setNullable(true); + dataSchema.setDescription("데이터 (에러 응답에서는 항상 null)"); + voidSchema.addProperty("data", dataSchema); + + // 스키마 등록 + openApi.getComponents().addSchemas("ApiResponseDtoVoid", voidSchema); + }; + } } \ No newline at end of file From b7e420c40ab00097a6e0564384f5316a2679d7e5 Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 22 Jan 2026 22:44:40 +0900 Subject: [PATCH 3/5] feat(SwaggerErrorCodeExampleCustomizer): update error response handling with new ResponseCode mappings and adjust schema reference to ApiResponseDtoVoid --- .../SwaggerErrorCodeExampleCustomizer.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java b/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java index c2a9f7d..3aa8928 100644 --- a/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java +++ b/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java @@ -65,10 +65,10 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { apiResponse.getContent().addMediaType("application/json", mediaType); } - // Schema 설정 (ApiResponseDto 참조) + // Schema 설정 (ApiResponseDto 참조 - 에러 응답은 data가 null이므로 ApiResponseDtoVoid로 생성됨) if (mediaType.getSchema() == null) { io.swagger.v3.oas.models.media.Schema schema = new io.swagger.v3.oas.models.media.Schema<>(); - schema.set$ref("#/components/schemas/ApiResponseDto"); + schema.set$ref("#/components/schemas/ApiResponseDtoVoid"); mediaType.setSchema(schema); } @@ -142,6 +142,8 @@ private ResponseCode mapErrorCodeToResponseCode(ErrorCode errorCode) { case TRIP_NAME_REQUIRED -> ResponseCode.TRIP_NAME_REQUIRED; case TRIP_NAME_TOO_LONG -> ResponseCode.TRIP_NAME_TOO_LONG; case TRIP_DESCRIPTION_TOO_LONG -> ResponseCode.TRIP_DESCRIPTION_TOO_LONG; + case NOT_PAST_TRIP -> ResponseCode.NOT_PAST_TRIP; + case CANNOT_ADD_DIARY_TO_PAST_TRIP -> ResponseCode.CANNOT_ADD_DIARY_TO_PAST_TRIP; case KAKAO_LOGIN_REQUIRED -> ResponseCode.KAKAO_LOGIN_REQUIRED; case INCOMPLETE_USER_INFO -> ResponseCode.INCOMPLETE_USER_INFO; case ALREADY_JOINED -> ResponseCode.ALREADY_JOINED; @@ -151,6 +153,29 @@ private ResponseCode mapErrorCodeToResponseCode(ErrorCode errorCode) { case FILE_TOO_LARGE -> ResponseCode.FILE_TOO_LARGE; case FILE_TYPE_NOT_ALLOWED -> ResponseCode.FILE_TYPE_NOT_ALLOWED; case FILE_UPLOAD_FAILED -> ResponseCode.FILE_UPLOAD_FAILED; + // 보관 + case TRIP_NOT_STORED -> ResponseCode.TRIP_NOT_STORED; + case DIARY_NOT_STORED -> ResponseCode.DIARY_NOT_STORED; + // 카카오 로그인 + case KAKAO_TOKEN_MISSING -> ResponseCode.KAKAO_TOKEN_MISSING; + case KAKAO_INVALID_TOKEN -> ResponseCode.KAKAO_INVALID_TOKEN; + case KAKAO_UNAUTHORIZED -> ResponseCode.KAKAO_UNAUTHORIZED; + case KAKAO_SERVER_ERROR -> ResponseCode.KAKAO_SERVER_ERROR; + case KAKAO_RESPONSE_PARSING_ERROR -> ResponseCode.KAKAO_RESPONSE_PARSING_ERROR; + case KAKAO_API_UNKNOWN_ERROR -> ResponseCode.KAKAO_API_UNKNOWN_ERROR; + // 보드 + case BOARD_REQUIRED_FIELDS_MISSING -> ResponseCode.BOARD_REQUIRED_FIELDS_MISSING; + case BOARD_THEME_NOT_FOUND -> ResponseCode.BOARD_THEME_NOT_FOUND; + case BOARD_CREATE_FAILED -> ResponseCode.BOARD_CREATE_FAILED; + case BOARD_NOT_FOUND -> ResponseCode.BOARD_NOT_FOUND; + case BOARD_UPDATE_FORBIDDEN -> ResponseCode.BOARD_UPDATE_FORBIDDEN; + case BOARD_STICKER_NOT_FOUND -> ResponseCode.BOARD_STICKER_NOT_FOUND; + case BOARD_UPDATE_INTERNAL_ERROR -> ResponseCode.BOARD_UPDATE_INTERNAL_ERROR; + case BOARD_DELETE_FORBIDDEN -> ResponseCode.BOARD_DELETE_FORBIDDEN; + case BOARD_STICKER_MAP_NOT_FOUND -> ResponseCode.BOARD_STICKER_MAP_NOT_FOUND; + case BOARD_ACCESS_FORBIDDEN -> ResponseCode.BOARD_ACCESS_FORBIDDEN; + // 알림 + case ALARM_NOT_AGREED -> ResponseCode.ALARM_NOT_AGREED; default -> ResponseCode.SERVER_ERROR; }; } From f69a9b6f9164ea09080792e51f57159266cea16e Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 22 Jan 2026 22:59:31 +0900 Subject: [PATCH 4/5] feat: update controllers to return ApiResponseDto for standardized response format --- .../zim/tave/memory/controller/EmotionController.java | 7 ++++--- .../zim/tave/memory/controller/JoinController.java | 7 +++---- .../zim/tave/memory/controller/MyPageController.java | 5 ++--- .../zim/tave/memory/controller/SettingController.java | 3 ++- .../zim/tave/memory/controller/StorageController.java | 1 - .../tave/memory/controller/TripThemeController.java | 6 ++++-- .../zim/tave/memory/controller/WeatherController.java | 11 +++++++---- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/main/java/zim/tave/memory/controller/EmotionController.java b/src/main/java/zim/tave/memory/controller/EmotionController.java index 0a4c4b2..3ab901a 100644 --- a/src/main/java/zim/tave/memory/controller/EmotionController.java +++ b/src/main/java/zim/tave/memory/controller/EmotionController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirements; @@ -14,6 +13,8 @@ import org.springframework.web.bind.annotation.RestController; import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.domain.Emotion; +import zim.tave.memory.global.common.ApiResponseDto; +import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.service.EmotionService; @@ -35,8 +36,8 @@ public class EmotionController { content = @Content) }) @GetMapping("") - public ResponseEntity> getAllEmotions() { + public ResponseEntity>> getAllEmotions() { List emotions = emotionService.getAllEmotions(); - return ResponseEntity.ok(emotions); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, emotions)); } } diff --git a/src/main/java/zim/tave/memory/controller/JoinController.java b/src/main/java/zim/tave/memory/controller/JoinController.java index 261f544..0df886a 100644 --- a/src/main/java/zim/tave/memory/controller/JoinController.java +++ b/src/main/java/zim/tave/memory/controller/JoinController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -45,17 +44,17 @@ public class JoinController { @ApiResponse(responseCode = "400", description = """ 잘못된 요청입니다. 다음 에러 코드가 발생할 수 있습니다: - KAKAO_LOGIN_REQUIRED: 카카오 로그인이 필요합니다. - """), + """, content = @Content), @ApiResponse(responseCode = "401", description = """ 인증 실패입니다. 다음 에러 코드가 발생할 수 있습니다: - AUTHENTICATION_FAILED - INVALID_TOKEN - UNAUTHORIZED_USER - """), + """, content = @Content), @ApiResponse(responseCode = "409", description = """ 중복 가입 오류입니다: - ALREADY_JOINED: 이미 가입된 사용자입니다. - """) + """, content = @Content) }) @PostMapping("/join") public ResponseEntity> join( diff --git a/src/main/java/zim/tave/memory/controller/MyPageController.java b/src/main/java/zim/tave/memory/controller/MyPageController.java index a1e5596..154e470 100644 --- a/src/main/java/zim/tave/memory/controller/MyPageController.java +++ b/src/main/java/zim/tave/memory/controller/MyPageController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -19,7 +18,6 @@ import zim.tave.memory.global.common.ApiResponseDto; import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; -import zim.tave.memory.security.CustomUserDetails; import zim.tave.memory.service.MyPageService; import zim.tave.memory.service.VisitedCountryService; @@ -93,7 +91,8 @@ public ResponseEntity> getMyPage( - USER_NOT_FOUND: 사용자를 찾을 수 없습니다. - VISITED_COUNTRY_NOT_FOUND: 방문한 국가 정보가 없습니다. """, content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류로 인해 방문 국가 조회에 실패하였습니다.") + @ApiResponse(responseCode = "500", description = "서버 오류로 인해 방문 국가 조회에 실패하였습니다.", + content = @Content) }) @GetMapping("/mypage-visited-countries") public ResponseEntity> getVisitedCountries( diff --git a/src/main/java/zim/tave/memory/controller/SettingController.java b/src/main/java/zim/tave/memory/controller/SettingController.java index 9eb9ec6..e2b0af2 100644 --- a/src/main/java/zim/tave/memory/controller/SettingController.java +++ b/src/main/java/zim/tave/memory/controller/SettingController.java @@ -84,7 +84,8 @@ public ResponseEntity> deleteAccount( 리소스를 찾을 수 없습니다: - USER_NOT_FOUND: 사용자를 찾을 수 없습니다. """, content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류로 인해 회원 정보 수정 실패") + @ApiResponse(responseCode = "500", description = "서버 오류로 인해 회원 정보 수정 실패", + content = @Content) }) @PatchMapping("/users/me") public ResponseEntity> updateUserInfo( diff --git a/src/main/java/zim/tave/memory/controller/StorageController.java b/src/main/java/zim/tave/memory/controller/StorageController.java index 987ea4b..3d5ac1d 100644 --- a/src/main/java/zim/tave/memory/controller/StorageController.java +++ b/src/main/java/zim/tave/memory/controller/StorageController.java @@ -18,7 +18,6 @@ import zim.tave.memory.global.common.ApiResponseDto; import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; -import zim.tave.memory.security.CustomUserDetails; import zim.tave.memory.service.StorageService; import java.util.List; diff --git a/src/main/java/zim/tave/memory/controller/TripThemeController.java b/src/main/java/zim/tave/memory/controller/TripThemeController.java index 48a9d7c..fb2419f 100644 --- a/src/main/java/zim/tave/memory/controller/TripThemeController.java +++ b/src/main/java/zim/tave/memory/controller/TripThemeController.java @@ -14,6 +14,8 @@ import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.domain.TripTheme; import zim.tave.memory.dto.response.TripThemeResponseDto; +import zim.tave.memory.global.common.ApiResponseDto; +import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.repository.TripThemeRepository; @@ -37,11 +39,11 @@ public class TripThemeController { @ApiResponse(responseCode = "500", description = "서버 내부 오류입니다.", content = @Content) }) - public ResponseEntity> getAllThemes() { + public ResponseEntity>> getAllThemes() { List themes = tripThemeRepository.findAllByOrderById(); List response = themes.stream() .map(TripThemeResponseDto::from) .collect(Collectors.toList()); - return ResponseEntity.ok(response); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, response)); } } diff --git a/src/main/java/zim/tave/memory/controller/WeatherController.java b/src/main/java/zim/tave/memory/controller/WeatherController.java index f9ecd56..a717646 100644 --- a/src/main/java/zim/tave/memory/controller/WeatherController.java +++ b/src/main/java/zim/tave/memory/controller/WeatherController.java @@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.*; import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.dto.response.WeatherResponseDto; +import zim.tave.memory.global.common.ApiResponseDto; +import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.service.WeatherService; @@ -34,9 +36,9 @@ public class WeatherController { @ApiResponse(responseCode = "500", description = "서버 오류입니다.", content = @Content) }) - public ResponseEntity> getAllWeathers() { + public ResponseEntity>> getAllWeathers() { List weathers = weatherService.getAllWeathers(); - return ResponseEntity.ok(weathers); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, weathers)); } @GetMapping("/{weatherId}") @@ -47,9 +49,10 @@ public ResponseEntity> getAllWeathers() { @ApiResponse(responseCode = "404", description = "날씨를 찾을 수 없습니다.\n- WEATHER_NOT_FOUND: 날씨를 찾을 수 없습니다.", content = @Content) }) - public WeatherResponseDto getWeatherById( + public ResponseEntity> getWeatherById( @Parameter(description = "날씨 ID", required = true, example = "1") @PathVariable Long weatherId) { - return weatherService.getWeatherById(weatherId); + WeatherResponseDto weather = weatherService.getWeatherById(weatherId); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, weather)); } } From c43cb1eb58a57d138c221d07120c58460d1b9662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=95=EC=9B=90?= Date: Fri, 23 Jan 2026 00:53:42 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix(config):=20openApi.getComponents()=20nu?= =?UTF-8?q?ll=20=EA=B0=80=EB=8A=A5=EC=84=B1=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20NPE=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - openApi.getComponents() null 가능성으로 인한 NPE 방지 - Swagger v3 버전 의존적인 addProperty() 사용 제거 → 컴파일 안정성 확보 - Map + setProperties() 방식으로 모든 환경에서 안전하게 스키마 등록 기능/설계 변경 없이 안정성 보완만 수행 --- .../zim/tave/memory/config/OpenApiConfig.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/zim/tave/memory/config/OpenApiConfig.java b/src/main/java/zim/tave/memory/config/OpenApiConfig.java index 5e4e7cd..9dc3d86 100644 --- a/src/main/java/zim/tave/memory/config/OpenApiConfig.java +++ b/src/main/java/zim/tave/memory/config/OpenApiConfig.java @@ -36,6 +36,12 @@ public class OpenApiConfig { @Bean public OpenApiCustomizer apiResponseDtoVoidSchemaCustomizer() { return openApi -> { + + // Components가 null일 경우 NPE 방지 + if (openApi.getComponents() == null) { + openApi.setComponents(new Components()); + } + // ApiResponseDto 스키마를 명시적으로 등록 Schema voidSchema = new Schema<>(); voidSchema.setType("object"); @@ -56,9 +62,16 @@ public OpenApiCustomizer apiResponseDtoVoidSchemaCustomizer() { dataSchema.setNullable(true); dataSchema.setDescription("데이터 (에러 응답에서는 항상 null)"); voidSchema.addProperty("data", dataSchema); + + // addProperty 사용 제거 → Map + setProperties 방식으로 안전하게 설정 + Map> properties = new LinkedHashMap<>(); + properties.put("code", codeSchema); + properties.put("message", messageSchema); + properties.put("data", dataSchema); + voidSchema.setProperties(properties); // 스키마 등록 openApi.getComponents().addSchemas("ApiResponseDtoVoid", voidSchema); }; } -} \ No newline at end of file +}