Search

Multipart/form-data์™€ Validation์˜ ๊ด€๊ณ„

ย ๋ฐฐ๊ฒฝ

์–ด๋Š API์—์„œ ํŒŒ์ผ๊ณผ DTO๋ฅผ ๊ฐ™์ด ๋ฐ›๊ธฐ ์œ„ํ•ด Multipart/form-data ๋ผ๋Š” content-type์„ ์‚ฌ์šฉํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.
"file": ํŒŒ์ผ์˜ blob "json": stringfy๋œ json
Plain Text
๋ณต์‚ฌ
form-data์—” ์œ„ ๋ฐฉ์‹๋Œ€๋กœ ๋ฐ›๊ณ  ์žˆ์—ˆ๊ณ  ๊ฐ key-value์Œ์˜ ํŒŒํŠธ๋กœ ๊ตฌ๋ถ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
ํด๋ผ์ด์–ธํŠธ์ธก์—๋Š” json ํŒŒํŠธ์˜ content-type์„ application/json์œผ๋กœ ๋”ฐ๋กœ ์ง€์ •ํ•ด์„œ ์š”์ฒญํ•ด๋‹ฌ๋ผ๊ณ  ํ•˜์˜€์ง€๋งŒ
์ด๋ฅผ ์ด์šฉํ•˜๋Š”๋ฐ ์–ด๋ ค์›€์ด ์žˆ์–ด์„œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์„œ ์„ ํƒ์ง€๋Š” 2๊ฐ€์ง€์˜€์Šต๋‹ˆ๋‹ค.
1.
json์˜ ๊ฐ ํ•„๋“œ๋ฅผ ๋ฐฑ์—”๋“œ์ธก์—์„œ Map์œผ๋กœ ๋ฐ›๋Š” ๋ฐฉ๋ฒ•
2.
text/plain ํƒ€์ž…์œผ๋กœ ๋ฐ›์€ ๋’ค ObjectMapper๋‚˜ Gson์„ ์‚ฌ์šฉํ•ด DTO๋กœ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•
1๋ฒˆ์˜ ๊ฒฝ์šฐ๋Š” ํ•„๋“œ๊ฐ€ ๋งŽ์•„์ง€๊ณ  value์˜ ํƒ€์ž…์ด ๋‹ค์–‘ํ•ด์ง€๋ฉด Map<String, Object>๋กœ ๋ฐ›์•„์•ผํ•ด์„œ ๊บผ๋‚ด๋Š” ๊ณผ์ •์—์„œ ๊ต‰์žฅํ•œ ๋ฒˆ๊ฑฐ๋กœ์›€๊ณผ ํƒ€์ž…์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ 2๋ฒˆ์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•œ ์ด์•ผ๊ธฐ์ž…๋‹ˆ๋‹ค..

ย ์‚ฌ๊ฑด์˜ ๋“ฑ์žฅ

@PutMapping(value = "/{companyId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<ResponseDTO> updateCompany(HttpServletRequest request, @RequestPart(required = false) MultipartFile logo, @Parameter(schema = @Schema(implementation = CompanyUpdateDTOforBiz.class)) @RequestPart String json, @PathVariable Long companyId) throws IOException { CompanyUpdateDTOforBiz dto = objectMapper.readValue(json, CompanyUpdateDTOforBiz.class); return new ResponseEntity<>(companyService.updateCompany(request, dto, logo, companyId), HttpStatus.OK); }
Java
๋ณต์‚ฌ
Controller์˜ ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™์•˜๊ณ  json์„ DTO๊ฐ€ ์•„๋‹Œ String์œผ๋กœ ๋ฐ›๋„๋ก ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค.
ObjectMapper๋ฅผ ์ด์šฉํ•˜์—ฌ DTO๋กœ ๋งคํ•‘ํ•˜๋Š”๋ฐ๊นŒ์ง„ ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆฌ์ž ์‚ฌ๊ฑด์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
์‹คํŒจํ•˜๋Š” ์กฐ๊ฑด์˜ ํ…Œ์ŠคํŠธ๋งŒ ๋ชจ์กฐ๋ฆฌ ์‹คํŒจํ•˜๋Š” ๊ฒƒ์„ ๋ณด๋‹ˆ Validation์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ์•Œ์•„์ฐจ๋ ธ์Šต๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์„œ ์ž ๊น ์Šคํ”„๋ง์˜ WebDataBind ๊ณผ์ •์„ ์‚ดํŽด๋ณด๊ณ  ๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.
์ด ์‚ฌ์ง„์€ Spring MVC ๊ตฌ์กฐ์—์„œ HTTP ์š”์ฒญ์—์„œ๋ถ€ํ„ฐ ์ปจํŠธ๋กค๋Ÿฌ๊นŒ์ง€๋ฅผ ํฌ๊ฒŒ ๋ณด์—ฌ์ฃผ๋Š” ์‚ฌ์ง„์ž…๋‹ˆ๋‹ค.
HTTP ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด DispatcherServlet์€ ํ•ธ๋“ค๋Ÿฌ ๋งคํ•‘์—๊ฒŒ ์–ด๋–ค ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ฒ˜๋ฆฌํ• ์ง€ ์ฐพ์œผ๋ผ๊ณ  ์‹œํ‚ต๋‹ˆ๋‹ค.
ํ•ธ๋“ค๋Ÿฌ ๋งคํ•‘์€ DispatcherServlet์—๊ฒŒ ์ ๋‹นํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ฐพ์•„์„œ ์•Œ๋ ค์ฃผ๊ณ  ํ•ธ๋“ค๋Ÿฌ ์–ด๋Œ‘ํ„ฐ์—๊ฒŒ ๊ทธ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ฐพ์•„์„œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋ผ๊ณ  ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
์ด์ œ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์‚ฌ์ง„์—” ํ‘œ์‹œ๋˜์ง€ ์•Š์€ Service ๋ ˆ์ด์–ด์™€ Repository ๋ ˆ์ด์–ด๋ฅผ ๊ฑฐ์ณ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
์ด์ œ ํ•ธ๋“ค๋Ÿฌ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์–ด๋– ํ•œ ์ผ์„ ํ•˜๋Š”์ง€ ๋” ์ž์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
์ €ํฌ๋Š” Controller ๋ ˆ์ด์–ด์— ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ HttpServletRequest, @RequestBody, @RequestParam, @ModelAttribute ๋“ฑ ๋งŽ์€ ์ข…๋ฅ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
DispatcherServlet์—๊ฒŒ ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋ผ๊ณ  ์ „๋‹ฌ ๋ฐ›์€ ํ•ธ๋“ค๋Ÿฌ ์–ด๋Œ‘ํ„ฐ๋Š” ์—ฌ๋Ÿฌ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์–ด๋…ธํ…Œ์ด์…˜ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ์— ์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
์ด ๋•Œ ๋งŽ์€ ์ข…๋ฅ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์€ ๋ฐ”๋กœ ArgumentResolver์ž…๋‹ˆ๋‹ค.
ArgumentResolver๋Š” ์ €ํฌ๊ฐ€ @RequestBody ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ DTO๋กœ ๋ฐ›๊ณ  ์‹ถ์–ดํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ๋งคํ•‘์„ HttpMessageConverter๋ฅผ ํ†ตํ•ด์„œ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
ArgumentResolver ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ HttpMessageConverter ๋˜ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ตฌํ˜„์ฒด๋ฅผ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ €์˜ ์ƒํ™ฉ์—์„  DTO๋กœ ๋ฐ›์„ ๋•Œ HttpMessageConverter์˜ ๊ตฌํ˜„์ฒด ์ค‘ Jackson2HttpMessageConverter๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ๊ฒƒ์„ String์œผ๋กœ ๋ฐ›๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ StringHttpMessageConverter๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
์ด์ œ ์ œ๊ฐ€ ์ž‘์„ฑํ•œ DTO๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
@Getter @Setter @NoArgsConstructor public class CompanyUpdateDTOforBiz { @NotNull(message = "์ง์› ์ˆ˜๊ฐ€ ์ž…๋ ฅ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", groups = ValidationGroups.NullCheckGroup.class) @Min(value = 1, message = "์ง์› ์ˆ˜๋Š” ์ตœ์†Œ 1๋ช… ์ด์ƒ์ด์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.") @Schema(name = "employee_number", description = "์ง์› ์ˆ˜", example = "100", required = true) @SerializedName("employee_number") private Integer employeeNumber; @NotBlank(message = "์ฃผ์†Œ๊ฐ€ ๊ณต๋ฐฑ์ด๊ฑฐ๋‚˜ ์ž…๋ ฅ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.") @Schema(description = "์ฃผ์†Œ", example = "์„œ์šธ ๊ฐ•๋‚จ๊ตฌ ํ…Œํ—ค๋ž€๋กœ 217", required = true) private String address; @NotBlank(message = "ํ•œ ์ค„ ์†Œ๊ฐœ๊ฐ€ ๊ณต๋ฐฑ์ด๊ฑฐ๋‚˜ ์ž…๋ ฅ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", groups = ValidationGroups.NullCheckGroup.class) @Length(max = 120, message = "ํ•œ ์ค„ ์†Œ๊ฐœ๊ฐ€ 120์ž๋ฅผ ์ดˆ๊ณผํ•˜์˜€์Šต๋‹ˆ๋‹ค.") @Schema(description = "ํ•œ ์ค„ ์†Œ๊ฐœ", example = "์ƒ์‚ฐ/์ „๋ฌธ์ง ์ „์šฉ ์ฑ„์šฉ ํ”Œ๋žซํผ", required = true) private String intro; ... }
Java
๋ณต์‚ฌ
์ œ๊ฐ€ ์ž‘์„ฑํ•œ DTO๋Š” ์œ„์™€ ๊ฐ™์ด validation group์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— @Valid ๋Œ€์‹  @Validated๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ–ˆ์Šต๋‹ˆ๋‹ค.
@Validated๋Š” javax ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์•„๋‹Œ ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ArgumentResolver๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„๋˜๋Š” ๋กœ์ง์ด ์•„๋‹Œ AOP๋ฅผ ํ†ตํ•ด์„œ ๋™์ž‘ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
์‚ฌ์‹ค @Valid๋ฅผ ์‚ฌ์šฉํ•˜๋“  @Validated๋ฅผ ์‚ฌ์šฉํ•˜๋“  ์ œ๊ฐ€ ๋ฐ›๋Š” json์€ String ํƒ€์ž…์ด๊ธฐ ๋•Œ๋ฌธ์— validation์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ย ํ•ด๊ฒฐํ•œ ๋ฐฉ๋ฒ•

์ด๋ฏธ String์œผ๋กœ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•œ ์ด์ƒ Controller ๋ ˆ์ด์–ด ์ดํ›„์—์„œ Validation ๊ณผ์ •์„ ์ง์ ‘ ํ•˜๊ธฐ๋กœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
์šฐ์„  String์—์„œ DTO๋กœ ๋งคํ•‘ํ•˜๋Š” ๊ณผ์ •์„ ํ”„๋กœ์ ํŠธ์—์„  Gson๊ณผ ObjectMapper ๋‘ ๊ฐ€์ง€๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์„œ MapperConfig ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๊ฐ๊ฐ์— ๋Œ€ํ•œ ์„ค์ •์„ ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์ค๋‹ˆ๋‹ค.
@Configuration public class MapperConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return objectMapper; } @Bean public Gson gson() { return new GsonBuilder() .registerTypeAdapter(LocalDate.class, new LocalDateSerializer()) .registerTypeAdapter(LocalDate.class, new LocalDateDeserializer()) .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()) .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeDeserializer()) .create(); } public static class LocalDateSerializer implements JsonSerializer<LocalDate> { @Override public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); } } public static class LocalDateDeserializer implements JsonDeserializer<LocalDate> { @Override public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return LocalDate.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd")); } } public static class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> { @Override public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))); } } public static class LocalDateTimeDeserializer implements JsonDeserializer<LocalDateTime> { @Override public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return LocalDateTime.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")); } } }
Java
๋ณต์‚ฌ
์œ„ ์ฝ”๋“œ์—์„œ ๋จผ์ € ObjectMapper๋ฅผ ๋ณด๋ฉด ๋‚ ์งœ/์‹œ๊ฐ„์— ๋Œ€ํ•œ ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ•ด์ค˜ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๊ณผ์ •์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
๋˜ํ•œ PropertyNamingStrategy๋ฅผ ์ง€์ •ํ•ด์ฃผ์–ด ๊ฐœ๋ฐœํŒ€ ๋‚ด์—์„œ ์•ฝ์†ํ•œ ๊ทœ์น™์ธ SNAKE_CASE๋กœ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.
๋งˆ์ง€๋ง‰์œผ๋กœ @JsonIgnore๊ฐ€ ๋ถ™์€ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ฌด์‹œํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •๊นŒ์ง€ ํ•ด์ค๋‹ˆ๋‹ค.
๊ทธ ์•„๋ž˜๋ถ€ํ„ฐ๋Š” Gson์— ๋Œ€ํ•ด ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๊ณผ์ • ์ค‘ ๋‚ ์งœ/์‹œ๊ฐ„์— ๋Œ€ํ•œ ํฌ๋งท ์„ค์ •์„ ํ•ด์ฃผ๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
๋งคํผ๋“ค์— ๋Œ€ํ•œ ์„ค์ •์ด ๋๋‚ฌ์œผ๋‹ˆ ์ด์ œ Controller๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
@PutMapping(value = "/{companyId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<ResponseDTO> updateCompany(HttpServletRequest request, @RequestPart(required = false) MultipartFile logo, @Parameter(schema = @Schema(implementation = CompanyUpdateDTOforBiz.class)) @RequestPart String json, @PathVariable Long companyId) throws IOException { CompanyUpdateDTOforBiz dto = objectMapper.readValue(json, CompanyUpdateDTOforBiz.class); Validator validator = validatorFactory.getValidator(); Set<ConstraintViolation<CompanyUpdateDTOforBiz>> constraints = validator.validate(dto); if (!constraints.isEmpty()) { String message = constraints.stream().map(ConstraintViolation::getMessage).findFirst().get(); throw new CustomException(Arrays.stream(ErrorCode.values()).filter(e -> e.getErrorMessage().equals(message)).findFirst().get()); } return new ResponseEntity<>(companyService.updateCompany(request, dto, logo, companyId), HttpStatus.OK); }
Java
๋ณต์‚ฌ
๋จผ์ € objectMapper๋ฅผ DI ๋ฐ›์•„์„œ String์œผ๋กœ ๋ฐ›์•„์˜จ json์„ DTO๋กœ ๋งคํ•‘ํ•ด์ค๋‹ˆ๋‹ค.
๊ทธ ๋‹ค์Œ๋ถ€ํ„ด Validation์„ ์ง์ ‘ ํ•ด์ฃผ๋Š” ๊ณผ์ •์œผ๋กœ ์ €๋ฅผ ๊ดด๋กญํ˜”๋˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
ValidatorFactory๋ฅผ DI ๋ฐ›์•„์„œ factory๋กœ๋ถ€ํ„ฐ validator๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ validate() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๊ฒ€์ฆ ๊ณผ์ •์„ ๊ฑฐ์ณ์ค๋‹ˆ๋‹ค.
๊ฒ€์ฆ ๊ณผ์ •์˜ ๊ฒฐ๊ณผ๋ฌผ๋กœ Set<ConstraintViolation<DTO>> ๋ฅผ ๋ฐ›๋Š”๋ฐ ์ด ์•ˆ์—๋Š” ๊ฒ€์ฆ์—์„œ ๋ฐœ์ƒํ•œ Validation ์—๋Ÿฌ๋“ค์ด ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.
์ด ์ดํ›„๋ถ€ํ„ฐ๋Š” ๊ฐœ์ธ์˜ ์ทจํ–ฅ์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋‚˜๋ฆ„์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
@Getter @AllArgsConstructor public enum ErrorCode { BLANK_MEMBER(404, "BLANK_MEMBER", "ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", HttpStatus.NOT_FOUND.name()), WRONG_EMAIL(400, "WRONG_EMAIL", "์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", HttpStatus.BAD_REQUEST.name()), BLANK_EMAIL(400, "BLANK_EMAIL", "์ด๋ฉ”์ผ์ด ๊ณต๋ฐฑ์ด๊ฑฐ๋‚˜ ์ž…๋ ฅ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST.name()), DUPLICATED_EMAIL(400, "DUPLICATED_EMAIL", "์ด๋ฏธ ๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ ์ž…๋‹ˆ๋‹ค.", HttpStatus.BAD_REQUEST.name()), ... private final int status; private final String errorCode; private final String errorMessage; private final String statusName; }
Java
๋ณต์‚ฌ
์ €์˜ ๊ฒฝ์šฐ์—๋Š” ๋ชจ๋“  ์—๋Ÿฌ์ฝ”๋“œ์— ๋Œ€ํ•ด์„œ Enum ํด๋ž˜์Šค ํ•˜๋‚˜๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
๋งŒ์•ฝ ๊ฒ€์ฆ๊ณผ์ •์—์„œ ์—๋Ÿฌ๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ํ•ด๋‹น ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ Exception์„ ํ„ฐํŠธ๋ ค ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) protected ResponseEntity<ErrorResponseDTO> handleCustomException(CustomException e, HttpServletRequest request) { ErrorResponseDTO error = ErrorResponseDTO.builder() .status(e.getErrorCode().getStatus()) .errorMessage(e.getErrorCode().getErrorMessage()) .errorCode(e.getErrorCode().getErrorCode()) .statusName(e.getErrorCode().getStatusName()) .path(request.getRequestURI()) .build(); return new ResponseEntity<>(error, HttpStatus.valueOf(e.getErrorCode().getStatus())); } }
Java
๋ณต์‚ฌ
CustomException(ErrorCode errorCode) ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฏธ๋ฆฌ ์„ค์ •ํ•ด์ค€ ExceptionHandler๊ฐ€ ErrorResponse๋ฅผ ๋งŒ๋“ค์–ด ์‘๋‹ตํ•ด์ค๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒํ•ด์„œ Multipart/form-data ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์— ๋”ฐ๋ผ json์„ ๊ฑด๋‚ผ ๋•Œ์˜ ํƒ€์ž…์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ ์˜๋„์น˜์•Š๊ฒŒ ๊ทธ ๋™์•ˆ Swagger์—์„  application/octet-stream์œผ๋กœ ์„ค์ •๋ผ์„œ ์ •์ƒ์ ์ธ ์š”์ฒญ์ด ์•ˆ๋˜๋˜ ๊ฒƒ๋„ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.