📘 Backend/Spring

Open API 3.0 Swagger + Export Rest API Docs

신건우 2024. 10. 4. 16:46

📚 Swagger API 적용

사내에서 대부분 시스템쪽 개발을 맡다가 이번 프로젝트에서 맡은 서버들 중에 API쪽 서버가 있어 기억 속에서 잊혀져 가던 Swagger를 다시 복습할 겸 정리글을 작성합니다.


Spring Doc 공식 페이지

Spring을 사용한다면 아래 2개의 라이브러리를 사용할 수 있습니다.

  • Spring Fox
  • Spring Doc

Spring Fox의 경우 마지막 업데이트일인 2020년 이후 업데이트가 없으므로, Spring Doc을 이용하겠습니다.

Spring Doc은 WebFlux를 통한 비동기 방식의 개발도 지원합니다.


application.yml 설정해서 Path 설정을 안한다면 기본 Path는 {Server I{}:{Port}/swagger-ui/index.html입니다.

저는 그냥 포트뒤에 바로 index.html을 붙이면 들어갈 수 있게 설정하겠습니다.


📚 Configuration

build.gradle

  • 버전은 공식 홈페이지에서 확인해서 넣어줍니다.
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

application.yml

  • 세부 Property는 여기서 확인 가능합니다.
# Swagger
springdoc:
  api-docs:
    path: /api-docs # API 문서 생성 경로
    groups:
      enabled: true
  swagger-ui:
    path: /index.html # Swagger-ui 경로
    enabled: true
    groups-order: asc
    tags-sorter: alpha
    operations-sorter: alpha
    display-request-duration: true
    doc-expansion: none
  cache:
    disabled: true
  override-with-generic-response: false
  model-and-view-allowed: true
  default-consumes-media-type: application/json
  default-produces-media-type: application/json

OpenApiConfig

  • @OpenAPIDefinition을 이용해 전체 API 문서의 타이틀, 설명, 버전을 넣어줍니다.
  • openApi Bean : Security Schema를 이용해 Swagger에서 Authorization Header에 JWT 토큰을 입력할 수 있게 해줍니다.
  • sortSchemasAlphabetically Bean : Swagger 상의 Schema(DTO)에 Alphabetically Sort를 적용합니다.
@Configuration
@OpenAPIDefinition(info = @io.swagger.v3.oas.annotations.info.Info(
        title = "Producer-Consumer Pattern API",
        description = "API Docs for Producer-Consumer Pattern",
        version = "1.0.0"
))
public class OpenApiConfig {
    // Authorization Header 추가
    @Bean
    public OpenAPI openAPI() {
        SecurityScheme securityScheme = new SecurityScheme()
                .type(SecurityScheme.Type.HTTP)
                .scheme("bearer")
                .bearerFormat("JWT")
                .in(SecurityScheme.In.HEADER)
                .name("Authorization");

        SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth");

        return new OpenAPI()
                .components(new Components().addSecuritySchemes("bearerAuth", securityScheme))
                .security(Arrays.asList(securityRequirement));
    }

    // Sort Schema Alphabetically
    @Bean
    public OpenApiCustomizer sortSchemasAlphabetically() {
        return openApi -> {
            Map<String, Schema> schemas = openApi.getComponents().getSchemas();
            openApi.getComponents().setSchemas(new TreeMap<>(schemas));
        };
    }
}

🧙‍♀️ 정렬되기 전 Schema

  • 정렬되지 않은 Schema는 읽기가 매우 불편합니다.

🧙‍♀️ 정렬된 Schema

  • 편-안

📚 Annotaions

@OpenAPIDefinition

  • 전체 API에 대한 정보를 지정하는데 사용합니다.
  • INFO 속성을 사용하여 API의 제목, 설명, 버전 등을 설정할 수 있습니다.
@OpenAPIDefinition(info = @Info(title = "title", description = "desc", version = "1.0.0"))

@Tag

  • 보통 클래스에 함수에 붙여주며, 그룹화 하거나 분류하기 위해 사용됩니다.
@Tag(name = "User Add API", description = "유저 생성 API")
public class UserController {}

@Operation, @Parameter, @ApiResponse

  • @Operation : 특정 API 함수에 대한 설명을 붙여줍니다.
  • @Parameter : 함수 파라미터에 대한 이름, 설명을 붙여줍니다.
  • @ApiResponse : 특정 HTTP Status 코드에 대한 설명을 붙여줍니다.
@PreAuth(viewId = 0, authorization = AuthorizationType.Read)
@Operation(summary = "Get Users", description = "유저 전체 조회")
@ApiResponse(responseCode = "200", description = "Found User List")
@ApiResponse(responseCode = "400", description = "Invalid Parameter")
@ApiResponse(responseCode = "404", description = "Not Found")
@ApiResponse(responseCode = "405", description = "UnAuthorized Request")
@Parameter(name = "id", description = "유저 ID")
@GetMapping
public ResponseEntity getUsers(@PathVariable int id) {}

만약 MultiPartFormData 타입의 파일을 받으려면 아래와 같이 consumes와 produce 속성을 넣어줍니다.

@PreAuth(viewId = 0, authorization = AuthorizationType.Create)
@PostMapping(value = "/contents", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "컨텐츠 등록 (파일) / Multi Part Form Data & Json", description = "컨텐츠 등록 (파일) / Multi Part Form Data & Json")
@Parameter(name = "file", description = "이미지(png 등), 영상(mp4 등)", required = true)
@Parameter(
        name = "dto",
        description = "File을 제외한 나머지 데이터",
        content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ContentsDto.class)),
        required = true)
@ApiResponse(
        responseCode = "201",
        description = "컨텐츠 정보 반환",
        content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ContentsDto.Response.class))
)
public ResponseEntity createContents(CustomHttpServletRequest request,
                                     @RequestPart(value = "file") MultipartFile file,
                                     @RequestPart(value = "dto") @Valid ContentsDto.Create dto) {
    return new ResponseEntity(ApiResponseDto.makeResponse(contentsService.createContents(request, file, dto)), HttpStatus.CREATED);
}

@Hidden

  • 특정 API를 문서에 포함시키고 싶지 않을떄 단순히 붙여주면 됩니다.
@Hidden

@Schema

  • 보통 DTO 클래스에 사용하며 클래스나 필드에 붙여줍니다.
  • 메인 클래스 이름 위에는 title 속성을 써주고 클래스 하위 static class 에는 name 속성을 쓰고 필드에는 description으로 설명만 써주면 됩니다.
@Getter
public class UserRequest {

    @Data
    @Schema(name = "[ 유저 Request ] 생성", description = "유저 생성 Request")
    public static class Post {
        @NotNull
        @Schema(description = "유저명", example = "skw")
        private String name;
    }
}

📚 테스트

DTO

@Data
@Schema(title = "테스트 DTO", description = "Test API Request & Response")
public class OpenApiDto {

    @Schema(name = "테스트 Request")
    public static class Test {
        @Schema(description = "외부에서 받은 숫자", example = "1")
        private Integer num;
    }
}

Controller

@Tag(name = "Test Controller", description = "테스트 컨트롤러")
@RestController
@RequestMapping("/test")
public class OpenApiController {
    @Operation(summary = "Test Get", description = "테스트 GET")
    @ApiResponse(responseCode = "200", description = "Success")
    @GetMapping("/{num}")
    public ResponseEntity get(@PathVariable int num) {
        OpenApiDto dto = new OpenApiDto();
        dto.setNum(num);
        return new ResponseEntity(dto, HttpStatus.OK);
    }
}

서버를 실행시키고 {Server IP}:{Port}/index.html를 들어가보면 잘 나옵니다 ㅎ


📚 Export Rest API Docs

서버를 켜지 않았을 때 다른 부서에 API Docs를 보여주고 싶다면 아래와 같은 방법을 이용해 Swagger의 API들을 Export 할 수 있습니다.


1. 서버를 켜고 Swagger 상단의 Json 링크로 진입해 Json을 복사합니다.


2. Swagger Editor 사이트에 접속

  • Swagger Editor
  • 접속 후 나오자는 왼쪽 yaml 코드 입력 창에 복사한 Json을 붙여넣으면 Yaml로 변환됩니다.
  • 아래 사진에서 API에 잘못된 파라미터나 Yaml Syntex에 안맞는 부분들을 전부 수정해 빨간 에러 느낌표를 전부 없애줍니다.

  • 모든 에러를 잡았다면 아래와 사진의 1번과 같이 빨간줄이 사라집니다.
  • 이후 2번의 Generate Client -> 3번 HTML2를 선택하면 .zip 파일의 문서가 완성됩니다.

  • 마지막으로 알집을 풀어 index.html 파일을 열어보면 서버를 키지 않아도 정적인 Rest API Docs가 완성됩니다!