본문 바로가기

카테고리 없음

스웨거 코드를 컨트롤러 인터페이스로 분리하기

스웨거 코드가 최대한 프로젝트 코드를 해치지 않는 방법으로 각 컨트롤러들의 인터페이스를 만들어 준다. 인터페이스 안에 스웨거 코드를 적어준다.

FollowController(현재 존재하는 컨트롤러) -> FollowControllerDoc (새로 추가하는 인터페이스)

 

위에 사진처럼 controller 패캐지 안에 swaggerInterface 패캐지를 만들어서 저 안에 컨트롤러 인터페이스들을 보관하면 깔끔히 분리된다. 이 방법은 이렇게 컨트롤러에는 스웨거에 관한거는 implements FollowControllerDoc 이부분만 추가되기 때문에 프로젝트 코드가 스웨거코드에 의해 최소한으로 침혜된다.

public class FollowController implements FollowControllerDoc {

 

FollowController 안에 있는 api들을 복사해서 FollowControllerDoc에 가져온다.

단 인터페이스니까 구현부가 없어야 한다 바디{}와 어노테이션들을 다 정리 해주고 이제 스웨거 어노테이션을 잔뜩 올려주면 된다.

public interface FollowControllerDoc {
    
    Response<Void> followAMember(Authentication authentication, @PathVariable Long following);
    
    Response<Void> unfollowAMember(Authentication authentication, @PathVariable Long followingId);

    Response<Void> deleteAFollower(Authentication authentication, @PathVariable Long followerId);
    
    Response<Page<FollowListResponse>> followList(Authentication authentication, Pageable pageable, @PathVariable int or);
    
    }

@Tag(name="") 그룹화 

swagger-UI에서는 콘트롤러만다 기본으로 api들이 그룹으로 묶인다.

@Tag 를 인터페이스 맨 위에 달아주면 그 그룹 이름을 정할수 있다. 

@Tag(name = "팔로우APIs")
public interface FollowControllerDoc {

Tag는 description 까지 설정할수있지만 컨트롤러나 api의 @Tag는 그냥 name만 선언해주고 나중에 SwaggerConfig에서 순서 정해줄때 거기서 description까지 한번에 설정하는게 좋다.

예)@Tag(name = "팔로우APIs",description = "meep") 할수 있지만 SwaggerConfig에서 한번에 정의하기.

이렇게 Tag로 그룹 이름을 정할수있다. 

콘트롤러 위에 태그를 붙이면 그 안에 API들이 다 묶이지만 조금 더 섬세한 작업을 하고 싶다면 그 안에 API들마다 Tags()를 사용해서 묶어줄수 있다. 콘트롤러 위에랑 api위에 둘다 했다면 두 그룹에 똑같은 api가 존재할 것이다.

또 api에서도 tags에 {}를 사용하면 @Tags{"로그인 필요없는 APIs","팔로우 APIs"}를 사용하면 여러개의 그룹에 속하게 할수있다.

@Operation(summary = "게시글 검색 기능-좋아요 순", description = "로그인 없이 요청 가능한 기능입니다."
,tags = "공개APIs")
Response<Page<PostListResponse>> LikedList();
//한개의 그룹에 속한다

@Operation(summary = "게시글 검색 기능-좋아요 순", description = "로그인 없이 요청 가능한 기능입니다."
,tags = {"공개APIs","팔로우APIs"})
Response<Page<PostListResponse>> LikedList();
//두개의 그룹에 속한다

OpenAPiCustomizer 사용해서 Tag 태그, Api 순서 정렬해주기

여러쉬운 길을 찾았지만 안되더라 SwaggerConfig에 설정해주면 간단하게 된다. Tag 이름이랑 Description까지 적어주면 완벽!

    @Bean
    public OpenApiCustomizer customTagsOrder() {
        return openApi -> {
            openApi.setTags(Arrays.asList(
                    new Tag().name("공개APIs").description("로그인 필요없는 APIs: 회원가입, 로그인 API- 로그인/가입 후 response header에서 JWT 토큰을(Bearer) 복사해서 인증(Authorize) 해주세요. 이 글 위에 오른쪽에 [Authorize] 버튼누르고 붙여넣기하면 됩니다."),
                    new Tag().name("메인페이지APIs").description("게시글 좋아요순, 팔로우순 다건 조회"),
                    new Tag().name("게시글쓰기APIs").description("게시글 쓰기, 수정, 삭제 기능"),
                    new Tag().name("게시글조회APIs").description("게시글 단건조회, 다건조회(맴버 아이디로 특정맴버 조회, 내 게시글 조회)기능"),
                    new Tag().name("댓글쓰기APIs").description("게시글 댓글 쓰기, 수정, 삭제 기능"),
                    new Tag().name("좋아요APIs").description("특정 게시글 좋아요하기, 좋아요 취소하기 기능"),
                    new Tag().name("팔로우APIs").description("팔로우 기능+ 팔로우,팔로워 다건 조회 기능"),
                    new Tag().name("검색APIs").description("키워드 검색기능(게시글:최신순,추천순),(맴버:팔로워순,최근게시글순)"),
                    new Tag().name("토큰재발급APIs").description("토큰 재발급 요청을 합니다.")
            ));
        };
    }

위에 코드처럼 원하는 API tag 순서대로 적어주면 된다.  

@Configuration
public class SwaggerConfig {
    // JWT 인증 스키마 생성
    private SecurityScheme createAPIKeyScheme() {
        return new SecurityScheme()
                .type(SecurityScheme.Type.HTTP) // HTTP 기반 인증
                .bearerFormat("JWT") // Bearer 포맷 사용
                .scheme("bearer"); // Bearer 스키마
    }

    // OpenAPI 설정
    @Bean
    public OpenAPI OpenAPIConfig() {

        return new OpenAPI()
                // 인증 요구 사항 추가
                .addSecurityItem(new SecurityRequirement().addList("JWT")) // JWT 인증 추가
                .components(new Components()
                        .addSecuritySchemes("JWT", createAPIKeyScheme()))// JWT 인증 스키마
                .info(new Info()
                        .title("PIZZA KOALA API")
                        .description("PizzaKoala OpenAPI documentation. \n\n[JWT 인증 방법 영상 보기]X가림X 로그인api에서 jwt토큰을 발급받아서 수동으로 인증해주세요.") // 마크다운 형식으로 영상 링크 추가
                        .version("1.0"));

    }
    @Bean
    public OpenApiCustomizer customTagsOrder() {
        return openApi -> {
            openApi.setTags(Arrays.asList(
                    new Tag().name("공개APIs").description("로그인 필요없는 APIs: 회원가입, 로그인 API- 로그인/가입 후 response header에서 JWT 토큰을(Bearer) 복사해서 인증(Authorize) 해주세요. 이 글 위에 오른쪽에 [Authorize] 버튼누르고 붙여넣기하면 됩니다."),
                    new Tag().name("메인페이지APIs").description("게시글 좋아요순, 팔로우순 다건 조회"),
                    new Tag().name("게시글쓰기APIs").description("게시글 쓰기, 수정, 삭제 기능"),
                    new Tag().name("게시글조회APIs").description("게시글 단건조회, 다건조회(맴버 아이디로 특정맴버 조회, 내 게시글 조회)기능"),
                    new Tag().name("댓글쓰기APIs").description("게시글 댓글 쓰기, 수정, 삭제 기능"),
                    new Tag().name("좋아요APIs").description("특정 게시글 좋아요하기, 좋아요 취소하기 기능"),
                    new Tag().name("팔로우APIs").description("팔로우 기능+ 팔로우,팔로워 다건 조회 기능"),
                    new Tag().name("검색APIs").description("키워드 검색기능(게시글:최신순,추천순),(맴버:팔로워순,최근게시글순)"),
                    new Tag().name("토큰재발급APIs").description("토큰 재발급 요청을 합니다.")
            ));
        };
    }
}

 

안되면 왜떄문인지 나도 모르지만 혹시 모르니 yml파일 확인해보자

springdoc:
  api-docs:
    tags-sorter:
  swagger-ui:
    disable-swagger-default-url: true

이렇게만 해도 되던뎀...

 


@Operation 사용해서 api들 마다 설명 적어주기.

@Tag(name = "팔로우APIs")
public interface FollowControllerDoc {

    @Operation(summary = "맴버 팔로우 기능", description = "다른 유저를 팔로우하는 기능")
    Response<Void> followAMember(Authentication authentication, @PathVariable Long following);
    
    }

FollowControllerDoc에 이렇게 적어주면 api들 마다 기능 설명을 적을수 있다. 

그리구 아까 위에 @Tag에서 설명한것 처럼 api에 그룹으로 개별로 묶어줄려고 한다면 tags를 @Operation안에 사용하면 된다.

@Tag(name = "팔로우APIs")
public interface FollowControllerDoc {

    @Operation(summary = "맴버 팔로우 기능", description = "다른 유저를 팔로우하는 기능",tags = {"기능APIs","맴버APIs"})
    Response<Void> followAMember(Authentication authentication, @PathVariable Long following);
    
    }

한개만 tag할떈 {}대신 ()을 사용한다. tags=("맴버APIs")

 


@Parameter 사용해서 PathVariable에 들어갈 예제 미리 넣어주기

@PathVariable 앞에 @Paramemter를 적용해준다.

 @Parameter(
            description = "해당 게시글 아이디 입력. *성공예제를 먼저 실행해 주세요.*",
            examples = {
                    @ExampleObject(name = "예시1-성공", value = "4"),
                    @ExampleObject(name = "예시2-자신을 팔로우할 경우", value = "1"),
                    @ExampleObject(name = "예시3-이미 팔로우한 게시글일 경우", value = "2"),
                    @ExampleObject(name = "예시4-존재하지않는 계정일 경우", value = "10004")
            }
@Tag(name = "팔로우APIs")
public interface FollowControllerDoc {

    /**
     * 유저 팔로우 하기
     */

    @Operation(summary = "맴버 팔로우 기능", description = "다른 유저를 팔로우하는 기능",tags = {"팔로우APIs","맴버APIs"})
    Response<Void> followAMember(Authentication authentication,
                                 @Parameter(
            description = "해당 게시글 아이디 입력. *성공예제를 먼저 실행해 주세요.*",
            examples = {
                    @ExampleObject(name = "예시1-성공", value = "4"),
                    @ExampleObject(name = "예시2-자신을 팔로우할 경우", value = "1"),
                    @ExampleObject(name = "예시3-이미 팔로우한 게시글일 경우", value = "2"),
                    @ExampleObject(name = "예시4-존재하지않는 계정일 경우", value = "10004")
            }
    ) @PathVariable Long following);
    
    
    }

@APIResponses, APIResponse 사용해서 반환하는 코드 미리 작성해주기

response가 반환하는 형식과 다양한 에러코드 반환을 한눈에 알수있게 작성해준다.

햇갈리면 안되는데 복수인 @APIResponses(value={}) 안에 여러 response예제들을 작성해야한다 ->@APIResponse()

@ApiResponses(value = {
            @ApiResponse(),
            @ApiResponse(),
            @ApiResponse()
    })

 

@APIResponse안에는 이렇게 하나씩 작성해주면 된다.

            @ApiResponse(responseCode = "200", description = "유저를 팔로우합니다.",
                    content = @Content(mediaType = "application/json", schema = @Schema(implementation = PostCreateRequest.class),
                            examples = @ExampleObject(
                                    name = "맴버 팔로우 성공 예제",
                                    value = """
                                            {
                                               "resultCode": "SUCCESS"
                                             }
                                            """
                            )))

근데 어떤 경우에는 에러코드가 같지만 다른 exception을 반환해야할떄가 있다. 그럴떄는 examples=에 {}이걸 붙여주고 @ExampleObject들을 넣어주면 된다.

@ApiResponse(responseCode = "401", description = "자신의 계정을 팔로우하려 한 경우, 로그인을 안한 경우",
                    content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorCode.class),
                            examples = {@ExampleObject(
                                    name = "1_로그인해야 사용할수있는 기능입니다",
                                    value = """
                                            {
                                              "resultCode": "INVALID_TOKEN",
                                              "message": "Full authentication is required to access this resource"
                                            }
                                            """
                            ), @ExampleObject(
                                    name = "2_자신의 계정을 팔로우하려 한 경우",
                                    value = """
                                            {
                                               "resultCode": "INVALID_PERMISSION",
                                               "message": "Permission is invalid., You cannot follow your own account."
                                             }
                                            """
                            )}))

 

최종 코드 

    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "유저를 팔로우합니다.",
                    content = @Content(mediaType = "application/json", schema = @Schema(implementation = PostCreateRequest.class),
                            examples = @ExampleObject(
                                    name = "맴버 팔로우 성공 예제",
                                    value = """
                                            {
                                               "resultCode": "SUCCESS"
                                             }
                                            """
                            ))),
            @ApiResponse(responseCode = "401", description = "자신의 계정을 팔로우하려 한 경우, 로그인을 안한 경우",
                    content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorCode.class),
                            examples = {@ExampleObject(
                                    name = "1_로그인해야 사용할수있는 기능입니다",
                                    value = """
                                            {
                                              "resultCode": "INVALID_TOKEN",
                                              "message": "Full authentication is required to access this resource"
                                            }
                                            """
                            ), @ExampleObject(
                                    name = "2_자신의 계정을 팔로우하려 한 경우",
                                    value = """
                                            {
                                               "resultCode": "INVALID_PERMISSION",
                                               "message": "Permission is invalid., You cannot follow your own account."
                                             }
                                            """
                            )}))
    })
    @Operation(summary = "맴버 팔로우 기능", description = "다른 유저를 팔로우하는 기능")
    Response<Void> followAMember(Authentication authentication, @Parameter(
            description = "해당 게시글 아이디 입력. *성공예제를 먼저 실행해 주세요.*",
            examples = {
                    @ExampleObject(name = "예시1-성공", value = "4"),
                    @ExampleObject(name = "예시2-자신을 팔로우할 경우", value = "1"),
                    @ExampleObject(name = "예시3-이미 팔로우한 게시글일 경우", value = "2"),
                    @ExampleObject(name = "예시4-존재하지않는 계정일 경우", value = "10004")
            }
    ) @PathVariable Long following);

 

 

이정도면 된거 같당..

RequestPart이랑 RequestBody에 예제 미리 담아 두는 방법은 이 블로그에 적어뒀으니 궁금하시면 보세효.. 이건 그냥 스웨거용 어노테이션을 콘트롤러에 적용하는 법인데 우린 위에 인터페이스에 적용하는법 내내 알아봤으니 잘할수있을거다요..

https://what-is-coding.tistory.com/44

 

간단한 스웨거 사용 법

직접 어노테이션을 앤드포인트에 달아주는게 가장 간단하지만 내 프로젝트 코드보다 스웨거 코드가 더 많아지기 떄문에 일단 이해하는 용으로만 보고 스웨거용 코드는 콘트롤러의 인터페이스

what-is-coding.tistory.com

 

 

요기 밑에 추가로 스웨거로 사진 올리는 법이랑 토큰 인증 하는법 올릴거당 토큰 인증은 다행이 포스트맨보다 간편하다! *-*b

https://what-is-coding.tistory.com/49 

 

스웨거 사진 업로드 하는 법

일단 consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} 을 컨트롤러 api에 선언해줘야지 스웨거에서 파일을 올릴수있는 버튼이 생긴다.@PostMapping(value = "/join", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})그렇게 실행

what-is-coding.tistory.com

 

//토큰

 

//달력?! 시간