Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import kr.ac.kookmin.stream.common.CommonErrorCode;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberErrorCode;
import kr.ac.kookmin.stream.welfare.domain.fee.domain.FeeErrorCode;
import org.springdoc.core.annotations.ParameterObject;

/**
* 운영진 학생회비 API의 문서 명세. 구현은 {@link AdminFeeController}가 맡는다.
Expand All @@ -37,7 +38,7 @@ ApiResponse<PageResponse<AdminFeeSearchResponse>> getRequests(
AdminApiUser apiUser,
String status,
String keyword,
PageParams pageParams
@ParameterObject PageParams pageParams
);

/** 납부 확인 요청 처리. 승인/반려로 상태를 바꾸고 납부자 명부에 반영한다. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import kr.ac.kookmin.stream.api.app.event.archive.response.ArchiveListResponse;
import kr.ac.kookmin.stream.common.CommonErrorCode;
import kr.ac.kookmin.stream.event.domain.archive.domain.ArchiveErrorCode;
import org.springdoc.core.annotations.ParameterObject;

/**
* 학생 앱 행사 아카이브 API의 문서 명세. 구현은 {@link AppArchiveController}가 맡는다.
Expand All @@ -23,7 +24,7 @@ public interface AppArchiveApi {
@Operation(summary = "행사 아카이브 목록 조회",
description = "지난 행사 아카이브 목록과 필터용 연도 목록을 함께 조회한다. year를 지정하면 해당 연도만 필터링한다.")
@ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
ApiResponse<ArchiveListResponse> getArchives(ArchiveListParams params);
ApiResponse<ArchiveListResponse> getArchives(@ParameterObject ArchiveListParams params);

/** 아카이브 상세. */
@Operation(summary = "행사 아카이브 상세 조회")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import kr.ac.kookmin.stream.api.app.event.event.response.EventListItemResponse;
import kr.ac.kookmin.stream.common.CommonErrorCode;
import kr.ac.kookmin.stream.event.domain.event.domain.EventErrorCode;
import org.springdoc.core.annotations.ParameterObject;

/**
* 학생 앱 행사 API의 문서 명세. 구현은 {@link AppEventController}가 맡는다.
Expand All @@ -32,7 +33,7 @@ public interface AppEventApi {
description = "게시된 행사를 커서 기반으로 조회한다. recruitStatus로 모집 상태를 필터링하고, cursor/size로 다음 페이지를 넘긴다.")
@ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
@ApiErrorCode(type = EventErrorCode.class, codes = {"EVENT_INVALID_RECRUIT_STATUS", "EVENT_INVALID_CURSOR"})
ApiResponse<CursorSliceResponse<EventListItemResponse>> getEvents(EventListParams params);
ApiResponse<CursorSliceResponse<EventListItemResponse>> getEvents(@ParameterObject EventListParams params);

/** 행사 상세. */
@Operation(summary = "행사 상세 조회")
Expand Down Expand Up @@ -67,7 +68,7 @@ ApiResponse<EventApplyResponse> apply(
@ApiErrorCode(type = EventErrorCode.class, codes = {"EVENT_INVALID_CURSOR"})
ApiResponse<CursorSliceResponse<EventApplicationListItemResponse>> getMyApplications(
AppApiUser apiUser,
EventApplicationListParams params
@ParameterObject EventApplicationListParams params
);

/** 내 행사 신청 상세. 행사의 질문 전체에 이 신청의 답변을 붙여 내려준다. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import kr.ac.kookmin.stream.api.common.ApiUser;
import kr.ac.kookmin.stream.api.common.openapi.ApiErrorCodeCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.utils.SpringDocUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand All @@ -19,6 +21,11 @@ public class OpenApiConfig {

private static final String JWT_SCHEME = "bearerAuth";

static {
// ArgumentResolver로 주입되는 인증 사용자(ApiUser 구현체)를 springdoc이 쿼리 파라미터로 오인하지 않도록 무시한다.
SpringDocUtils.getConfig().addRequestWrapperToIgnore(ApiUser.class);
}

@Bean
public OpenAPI openAPI() {
return new OpenAPI()
Expand Down
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ subprojects {
}
}

// 메서드 파라미터 이름을 바이트코드에 보존한다. 미적용 시 @RequestParam 등이
// Swagger에서 arg0/arg1로 노출된다. (Spring Boot 플러그인 미적용 모듈까지 일괄 적용)
tasks.withType<JavaCompile> {
options.compilerArgs.add("-parameters")
}

// 모듈별 build.gradle.kts에서 반복 선언하지 않도록 lombok을 공통 의존성으로 적용
// (subprojects {} 클로저 내부 리시버 기준으로는 libs 카탈로그 액세서가 아직 등록되지 않아 rootProject를 통해 참조)
dependencies {
Expand Down
5 changes: 5 additions & 0 deletions docs/conventions/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public record Member(
- Request/Response DTO는 `record`(`api:{client}-api`). 각 도메인 패키지 아래 `request`/`response` 하위 패키지로 나눠 둔다(`{basePackage}.api.{client}.{팀}.{도메인}.{request|response}`, `architecture.md` 2-2절).
- Request DTO를 Service로 그대로 넘기지 않는다. `toCommand()`로 Command(`core:domain`)로 변환한다.
- 접미사로 요청 형태를 구분한다: **`@RequestBody` 바디 DTO는 `Request`**, **`@ModelAttribute`로 바인딩하는 쿼리 파라미터 DTO는 `Params`**(예: `EventListParams`, `ArchiveListParams`). 파일은 둘 다 `request` 하위 패키지에 둔다. 페이지네이션 공용 파라미터는 `common-api`의 `PageParams`를 쓴다.
- `Params` DTO 파라미터에는 `{Client}{Domain}Api` 인터페이스 쪽에 `@ParameterObject`(springdoc)를 붙인다. 없으면 Swagger에서 개별 필드로 펼쳐지지 않고 하나의 `object` 쿼리 파라미터로 뜬다. 컨트롤러에는 바인딩 어노테이션만 남긴다. (예: 인터페이스 `@ParameterObject EventListParams params`, 컨트롤러 `@Valid @ModelAttribute EventListParams params`)

```java
// api:admin-api
Expand Down Expand Up @@ -323,6 +324,10 @@ class MemberServiceImpl implements MemberService {
- role 전용 `ApiUser`(`config-and-auth.md`)와 `{Domain}Service`를 주입받는다. 단일 도메인 흐름은 Controller가 직접 처리한다.
- 클라이언트 접두사(`Admin`/`App`)로 컨트롤러를 구분하고, 각 클라이언트 모듈의 `{basePackage}.api.{client}.{팀}.{도메인}` 패키지 바로 아래 둔다(DTO는 그 아래 `request`/`response`로 분리, `architecture.md` 2-2절).
- Swagger 문서용 어노테이션(`@Tag`/`@Operation`/`@ApiErrorCode`)은 짝이 되는 `{Client}{Domain}Api` 인터페이스에 모으고, 컨트롤러가 이를 `implements`한다. 각 핸들러에 `@Override`를 붙이고 컨트롤러에는 라우팅·바인딩·본문만 남긴다(`error-handling.md` 6절).
- Swagger 쿼리 파라미터 렌더링 규칙:
- `ApiUser` 파라미터는 `OpenApiConfig`가 `SpringDocUtils.addRequestWrapperToIgnore(ApiUser.class)`로 문서에서 숨기므로 별도 처리하지 않는다.
- `@ModelAttribute` `Params` DTO에는 `-Api` 인터페이스의 같은 파라미터에 `@ParameterObject`를 붙인다(2-2절).
- 파라미터 이름은 루트 빌드의 `-parameters` 컴파일 옵션으로 보존되므로 `@RequestParam("name")`처럼 이름을 중복 명시하지 않아도 된다.

```java
// api:admin-api — 운영진 회원 등록 (Swagger 명세는 AdminMemberApi, error-handling.md 6절)
Expand Down