Feat: Swagger 커스텀 설정 추가

This commit is contained in:
김용수 2024-08-27 13:36:49 +09:00
commit bbdae072e5
8 changed files with 189 additions and 11 deletions

View File

@ -43,6 +43,9 @@ dependencies {
// GJson // GJson
implementation 'com.google.code.gson:gson:2.7' implementation 'com.google.code.gson:gson:2.7'
//Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
} }
tasks.named('test') { tasks.named('test') {

View File

@ -0,0 +1,58 @@
package com.worlabel.global.config;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@OpenAPIDefinition(
info = @Info(
title = "auto labeling API",
description = "auto labeling API 목록입니다.",
version = "v1.0"
)
)
@SecurityScheme(
name = "Authorization",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
in = SecuritySchemeIn.HEADER,
scheme = "Bearer",
description = "access token"
)
public class SwaggerConfig {
private final String[] noRequiredTokenApi = {"/register", "/login", "/reissue","/login/oauth", "/oauth2/**",
"/login/oauth2/**", "/error", "login/oauth2/code/kakao", "/register/duplicate", "/test"};
private final OperationCustomizer operationCustomizer;
public SwaggerConfig(OperationCustomizer operationCustomizer) {
this.operationCustomizer = operationCustomizer;
}
@Bean
public GroupedOpenApi nonSecurityGroup(){ //jwt 토큰 불필요한 api
return GroupedOpenApi.builder()
.group("token 불필요 API")
.pathsToMatch(noRequiredTokenApi)
.addOperationCustomizer(operationCustomizer)
.build();
}
@Bean
public GroupedOpenApi securityGroup(){ //jwt 토큰 필요한 api
return GroupedOpenApi.builder()
.group("token 필요 API")
.pathsToExclude(noRequiredTokenApi)
.addOperationCustomizer(operationCustomizer)
.build();
}
}

View File

@ -0,0 +1,71 @@
package com.worlabel.global.config.swagger;
import com.worlabel.global.exception.CustomException;
import com.worlabel.global.response.BaseResponse;
import com.worlabel.global.response.CustomError;
import com.worlabel.global.response.ErrorResponse;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import com.worlabel.global.exception.ErrorCode;
@Component
@AllArgsConstructor
public class CustomOperationCustomizer implements OperationCustomizer {
@Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
SwaggerApiError swaggerApiError = handlerMethod.getMethodAnnotation(SwaggerApiError.class);
operation.getResponses().remove("500");
if (swaggerApiError != null) {
generateErrorCodeResponse(operation, swaggerApiError.value());
}
return operation;
}
//에러 코드로 error response 만들어 ApiResponse 넣음
private void generateErrorCodeResponse(Operation operation, ErrorCode[] errorCodes) {
ApiResponses responses = operation.getResponses();
Map<Integer, List<BaseResponse<CustomError>>> statusWithErrorResponse = Arrays.stream(errorCodes)
.map(errorCode -> ErrorResponse.of(new CustomException(errorCode)))
.collect(Collectors.groupingBy(BaseResponse::getStatus));
addErrorCodesToResponse(responses, statusWithErrorResponse);
}
//ApiResponses에 error response 추가
private void addErrorCodesToResponse(ApiResponses apiResponses, Map<Integer, List<BaseResponse<CustomError>>> responses) {
responses.forEach((status, value) -> {
Content content = new Content();
MediaType mediaType = new MediaType();
ApiResponse apiResponse = new ApiResponse();
value.forEach(
errorInfoResponse -> {
Example example = new Example();
example.setValue(errorInfoResponse);
mediaType.addExamples(String.valueOf(errorInfoResponse.getCode()), example);
});
content.addMediaType("application/json", mediaType);
apiResponse.setContent(content);
apiResponses.addApiResponse(String.valueOf(status), apiResponse);
});
}
}

View File

@ -0,0 +1,16 @@
package com.worlabel.global.config.swagger;
import com.worlabel.global.exception.ErrorCode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerApiError {
ErrorCode[] value();
}

View File

@ -0,0 +1,20 @@
package com.worlabel.global.config.swagger;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ApiResponses(value = {
@ApiResponse(responseCode = "200")
})
public @interface SwaggerApiSuccess {
String description() default "";
}

View File

@ -22,6 +22,11 @@ public abstract class BaseResponse<T> {
@Getter(onMethod_ = @JsonProperty("isSuccess")) @Getter(onMethod_ = @JsonProperty("isSuccess"))
private boolean isSuccess; private boolean isSuccess;
/**
* 상태 코드
*/
private int status;
/** /**
* 응답 코드 * 응답 코드
*/ */
@ -42,8 +47,9 @@ public abstract class BaseResponse<T> {
*/ */
protected List<CustomError> errors; protected List<CustomError> errors;
public BaseResponse(boolean isSuccess, int code, String message) { public BaseResponse(boolean isSuccess, int status, int code, String message) {
this.isSuccess = isSuccess; this.isSuccess = isSuccess;
this.status = status;
this.code = code; this.code = code;
this.message = message; this.message = message;
this.data = null; this.data = null;

View File

@ -1,6 +1,7 @@
package com.worlabel.global.response; package com.worlabel.global.response;
import com.worlabel.global.exception.CustomException; import com.worlabel.global.exception.CustomException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
@ -16,19 +17,22 @@ import java.util.stream.Collectors;
* 에러 발생시 리턴 에러 응답 객체 * 에러 발생시 리턴 에러 응답 객체
*/ */
@Slf4j @Slf4j
public class ErrorResponse extends BaseResponse<Void> { @Getter
public class ErrorResponse extends BaseResponse<CustomError> {
public ErrorResponse(boolean isSuccess, int code, String message, Errors errors) {
super(isSuccess, code, message);
public ErrorResponse(boolean isSuccess, int status, int code, String message, Errors errors) {
super(isSuccess, status, code, message);
super.errors = parseErrors(errors); super.errors = parseErrors(errors);
} }
public ErrorResponse(CustomException exception) { public ErrorResponse(CustomException exception) {
this(false, exception.getErrorCode().getCode(), exception.getMessage(), exception.getErrors()); this(false, exception.getErrorCode().getStatus().value(), exception.getErrorCode().getCode(), exception.getMessage(), exception.getErrors());
} }
public ErrorResponse(CustomException exception, String message) { public ErrorResponse(CustomException exception, String message) {
this(false, 1, message, exception.getErrors()); this(false, exception.getErrorCode().getStatus().value(), 1, message, exception.getErrors());
} }
public static ErrorResponse of(CustomException exception) { public static ErrorResponse of(CustomException exception) {
@ -40,7 +44,7 @@ public class ErrorResponse extends BaseResponse<Void> {
} }
public static ErrorResponse of(Exception exception) { public static ErrorResponse of(Exception exception) {
return new ErrorResponse(false, HttpStatus.INTERNAL_SERVER_ERROR.value(), exception.getMessage(), null); return new ErrorResponse(false, HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.value(), exception.getMessage(), null);
} }
/** /**
@ -59,8 +63,8 @@ public class ErrorResponse extends BaseResponse<Void> {
// 글로벌 에러 리스트 생성 // 글로벌 에러 리스트 생성
List<CustomError> globalErrors = errors.getGlobalErrors().stream() List<CustomError> globalErrors = errors.getGlobalErrors().stream()
.map(e -> new CustomError(null, // 필드 이름이 없으므로 null .map(e -> new CustomError(null, // 필드 이름이 없으므로 null
e.getCode(), e.getCode(),
e.getDefaultMessage(), e.getDefaultMessage(),
e.getObjectName() e.getObjectName()
)) ))
.toList(); .toList();

View File

@ -17,7 +17,7 @@ public class SuccessResponse<T> extends BaseResponse<T> {
* 성공 응답 객체 생성자 * 성공 응답 객체 생성자
*/ */
public SuccessResponse() { public SuccessResponse() {
super(true, HttpStatus.OK.value(), "success"); super(true, HttpStatus.OK.value(), 200, "success");
} }
/** /**
@ -26,7 +26,7 @@ public class SuccessResponse<T> extends BaseResponse<T> {
* @param data 성공시 반환하는 데이터 * @param data 성공시 반환하는 데이터
*/ */
public SuccessResponse(T data) { public SuccessResponse(T data) {
super(true, 200, "success"); super(true, 200, 200, "success");
super.data = data; super.data = data;
} }