From 074be336d6e569a0b5c5f266033e67364b38e843 Mon Sep 17 00:00:00 2001 From: sunmingLee <25thbam@gmail.com> Date: Fri, 24 Oct 2025 14:13:23 +0900 Subject: [PATCH] =?UTF-8?q?distribution-service=20Swagger=20UI=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20API=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OpenApiConfig 추가: Swagger UI 기본 설정 및 API 정보 정의 - application.yml: Springdoc 설정 추가 및 웹 로깅 레벨 강화 - DistributionController: Swagger 어노테이션 추가 (@Tag, @Operation, @ApiResponses) - API path 주석 수정: /api prefix 제거하여 실제 경로와 일치 --- .../executionHistory/executionHistory.bin | Bin 275816 -> 397356 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 17 bytes .gradle/8.10/fileHashes/fileHashes.bin | Bin 25397 -> 26147 bytes .gradle/8.10/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../8.10/fileHashes/resourceHashesCache.bin | Bin 21217 -> 21455 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .gradle/file-system.probe | Bin 8 -> 8 bytes .../kt/distribution/config/OpenApiConfig.java | 52 +++++++++++++++ .../controller/DistributionController.java | 59 +++++++++++++++++- .../src/main/resources/application.yml | 14 +++++ 10 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 distribution-service/src/main/java/com/kt/distribution/config/OpenApiConfig.java diff --git a/.gradle/8.10/executionHistory/executionHistory.bin b/.gradle/8.10/executionHistory/executionHistory.bin index 3778430bf1cffdf4a829a73cbfc54ca5b7c22791..e2f2a1a06d8c071e8b09e83c5701107ad5f9088d 100644 GIT binary patch delta 4805 zcmeHKeOOFs8$ZuEr$VYDp%2kiQu?kS((PiZisD~re>PYnwgq}l8CYy#_L&H zAH7k_#>&v3eA`z@%8E80mAxpDSgiHBKhBMN01f;^^FH`5ajVg@;kKDw`Q2q z#(E(d!B@(xJuxFkhH8&><@8V|8iF{{@@1l>$+Bc_3P;Fh`LjL~NrnC#ra;OOrxK|@ zWSZ`?+wyis#x0(o>gxVx)WD={{DPsn`^7sPb-k(FDF0fuh;7y%%7)`y*bxa#Tvfm@fdV#wSa&4@TzARi14!~y%z%b~u0N^HF0jkBqN5HZ) zY31s}F(v2jl^rUg{mGjK$~B&2RSI!`WB6Mz@KgH8$Mq9Rxzbbs@|B74Rwis}bOAE# zP>#QL5EaZ}3WaR`Tq#p3lY~+ZT}vt0o0|6KB3j;X?a42n+rO2A!x|6=ZJc*+asQ&w zjVLe3n2K51biujXhF+MZDE2RUR(o*ow4tRyrGe{moWcQC#*||_nJJD6P00gF-$j1P z41QpL0aGkyC+Rk!9aT}&jMWaJLbwuX5;sOB<%)#borF^{T~jyYPBy*0VorIwaL=3c zs`|5##s));wXtw22MjjWx+_PBgvxJI2Pm;r8n{W#Ko0F)W{R0}*kX}{D-|WBc46*r z7|&}rA2NE{wMjGIuA<4odFUIB4uRXJV)kYq1i&Cy{Cms(UQKO^lgX*88ef($upMUL z%zJ+imHE;&DB)oqosia0ye(RJ*qT!x?4~@h#6fv}S+?1cM=iSx{1F_GHR+{f9NY=* z3&3F2%vC$8LY(~&#Mv)@>C$8&-!CveRu~r;!%h{;g?xEnpj4J3BUV;Vng4dh^H!U1 zx3mY()(x~Vu)skKbRk?50agGPwSs|;H-VEQ!DPsq2P9Obj{2}tS$w8Mf)V^&`^?Ce z7*+#6aTDtpe>1F|4~CNdsEMvn)#6AX0>o^us|vrSH4=+@ntBKP<+QqE0pOd0cF16X zBzo_4=-fk%O_Oo?vFs$Tt~I<8>?Kd7qFQH2GjKR(`xmzThr4& zV(OPWWT!2EZU(BU{dDURD$M+Yuv&d+TisgC7A}arKV(COAPWr<1)MmjZQ*WcY6cUv zL7~Y4R5GEDUUK%;p{}kjX=@nGJ6i?%ZOABo+zKi*8R*_m7TSK&g^;a8{^{|3OrV4B z_xrweu9=CcVJ9E4aw4o1;J7}#nHF9faE}7l^y%mi+D->&I(tth@7U|>V;oe97uf|y z;OF;^XK&R*M57MR6@J43MwS@yx}k~wok{GRkaI&%(5!OisvS6hhdR1nNQ~}GHooQg z!fEYol+vfFyj{nA;-;fUT;?PZIaN{$QIovhO;T)t+%thUc)pR@5IUod|jv)B- zkMTyQ(>7L53YCbrL66l2I6k4;i?gc<5<4mdQ1 z=SSe&z6HFfJa&uqrqUTVlq-uT8a;Lr&*{I^) z)+@*8oi=>>A0-WkUAq)nO)9uEN?dtpd3sc;b!+4O3*iw0TN8C{FeXRg0Z)7>-cfg> zQ2qUMYzCH|t>t}iqzA3)Rcdtlg%JAsb?Da{g$4fEzF=Dqcv?n7h;9l*qa^4I74>f^ zP#GSCD7}D1VVK1?`r>l9UNc$e2(Z;KOgERlf}EscZiJ9M(l+c(^0_fBmlEz*AZ)q$ zZfNR_mZfKFSsoM^VrqY-P81MAUq?DA>}(UTv$t5?s`Mpd4V8fQ*%%T{Z0M+DIpWu6 zW4p&rcB@_JkK`u3Kf8e}(yrS}B&+J2H>#ois zE*9oOlJKf5eO`T?C);-D>6i)a1vQ_qs_MGyC>7#d^(iNG7*MZu!ke*Ay!c@;Yp<05 zUtZL|rD%E43vBM;j(`~45wf#dNjbg@_j}^r#7or)Yg~S13_U&Llwj-YOla5&DUfKXLM3 zhxNb9$pW|j6K7x|{z#Wox5{!rG+B1}g22_ffWxFo|A5|&lSdAzw9S*P&Z_VpU{;egoaOsdwx$hAIw?+Wzxf}^oUeue delta 855 zcmZ{ic}SCS7{}k|w*yP8SKrxIIdoAGESJuy*+r+C)pT@N+2OW!c#JofIiU-Pu8<6IATZNGhn+oBe71{(AWEJP-W7pQrAatoIFFA(12M z&qHas6gA?;-`b+0sGb_Wj%?7~mR4p);E}SGXyHA)Q5Go>EPCcWCA*zCPu@vU@=jM# zoW`nlWEaHSErnKFp}}ZysIv_wHYTh(Xz=B(+WmA1ZXRW;FP(IA97l6Btp?!Xoy$I`XD4i*3%me-IDyTJ^Q8_b0lzu==()d(z*hLlOBa|kGDOqMC9F3 z^cApfi5hRsjVD?bhT_G=3V2L(qmh+pL9C)Tzo?z^){v+<;*(6996QcNe2i$0SG#2w zBIA|9VC-2Fj)mJwB_}WJcUQ+yLg4@P!P8adx+6t6_>2xaYEy}-RKUP%+`MNB)*cY! ziVLH#x-Jz*^)v9DrwO>VZYyzD@=bo+d15}fF9jhkCZV|L>?-1R1A}MP$Kl}@18~l{ zJhJ8(Fz}RbOvY?&0G{DV!cpHCcs@WnJUVih3`}^4yc!s9%J!<(q_9|u3X65cvDPd; zZPsQs>tk{>7E6xRQLt{KElxvUYCOn1l+Jtna$02Z=g`u(^dLgr0{q~#jQGchhLid< zh$6evpajXEkOvuHC-ozR;iO`&K*Sx@fltgWSOG=|xjD9F_R66}6B5dQUWR4U{cv?a z1jmc;Bd%>VEDhk&^@u^x@plbxVN%HU1S0gg>>ppj-L#!;!_;u&V6|9Hnm`=LwX!e~ zk?>TcAm>delkR?`YFzd6ipsh7%ll`I`AV!eC6UYx5K4C+O7!o37e9Ry+wtSlQs+HV zZbtiqDw_}Rrj&n7h0Vs!_A%VdY=PLrrH)2pW0OWS@TXkwinkY<9Y&+ZY|xv`+3Q)m yCd*P_$y3J^(bqpg@ZqwIpp==~rW9wOVAvq?pP;1w3hKDHv}HNsT9ymkFMk8HoK;}} diff --git a/.gradle/8.10/executionHistory/executionHistory.lock b/.gradle/8.10/executionHistory/executionHistory.lock index 778ae703f930bcf78ae246bed6ada1f3858ddff5..5d5f0de478077f3d5fc19a623aeeedef6fb838c5 100644 GIT binary patch literal 17 UcmZRsbes@(Px9DK1_($805WIm|4^_j;qBJ>ClfY7#XoMYmE6EA@ch@u=sOR8H$z4DZ&sDv z!6+cqp0#_o^l5X5Xo~*it8$k7{b$PCe)&W7C;y$yDnFCIYNY1oqw*e10?uuBd3=6+s)dLqpPtOBbduluYi0C~8~jl1-zOhcvJi;+ zl!Su#M` z0k@PPf~nDy_o@^z@nvpQ{2{*4U<2o7M~`FN3LA?WTel>wIs9nbre%shvYi+h7-xyh zj0lBj5^0{S9UMY+TJ_Jee=q8a^M5Yq&90ckyB zAfXAwiXbe2RmU8UOrRtX2<)2d8L!47GRI^3`PQ3@OChRM# zY`*_>O(63;U~B+`sN~LEhz_1+f6bVag z=1$aNsz-HWjs2$SJML^)v8L5++j*tg0w6uYuerM*dIZfdoq|mV&=XK!Vbig2Wo7m6 zlT5pBU3z!fs$TYQ1=wN5HzFXG@NdDiL|e-BxX*;;#>+ll+_2I~Y$HU+B@KuU?mA2z zx4P}P@6-kDuFqTgyZ+1#U?hV=sDKBeLsAu{V{&I4cl|BP?6-WP>Q0wzz5X^ULfli5 z#ST%+Rt{5(h|2G|t9A=$2XQG0vI$)Ie{M0@^wKrZ+$YhFsblZVnQ5=`T{f&c6nJ0$ z@I^7Gj)NdO>KPcUsxY;@5IL&(;KR418?_4U)wQ1?kzBgQ1f+$5fgg*fd7~mzo{IK2 z`hk7oMMze(5Z)sEZ&YxGb6gE%z(!P)tssmauvJ_`vYB?jj zG`Hmoi?*P~{XHdL7x#d*%t)UHv4o=rQ%6p>Y*WPZq~u#Ug_DwZ=1c+Wn8gH*L=I4a Gs|NrXL7xl& delta 443 zcmZ2{hH>jL#tkMCj9rsWC2mYsl{A=~E9tTMspJag%}ZrBFiy^uH=Mjpe!}E91^vxS z6&#o*|5Z9T`I@r+WLXu-&3-BYjFY#i#!uE&%VOeX+^G0Ne4~K`_hv_rW84Y~63<>3 zGCmb4`TnfmIyqZ}fq`+B@Nye31~A}!J6Szs4~sy6_Uy^eL-c@*to55EL+`P$2tS#n zGWlVs)MnABLyQWCRZcoZaaC$q_Wj+(7-stqY|2qFs3`)Po4-d(G4Ti=6<+``*WPY2 zW03Gr2O1lIawaTHh~V_M7P#<;Q|gC?_Uwy_C!n;5cg zh6R{z11mbeXg|a>o;yHV&lpH(01*B Za{;Ya`HaAdR$e^=QN(#`Gk2mE697Y?jOqXY diff --git a/.gradle/8.10/fileHashes/fileHashes.lock b/.gradle/8.10/fileHashes/fileHashes.lock index 4577896007bea6b094d682bc2b7981c78375319a..58f7b766bd1f8744697d733f54c288a1396fa31c 100644 GIT binary patch literal 17 VcmZS9`MA7`o8<*R0~jz?0st%t1JVEh literal 17 VcmZS9`MA7`o8<*R0~j#c0st$`1C{^) diff --git a/.gradle/8.10/fileHashes/resourceHashesCache.bin b/.gradle/8.10/fileHashes/resourceHashesCache.bin index 4320628bdf822d3491500c210841da4ae2445301..639a07bc1af1c0bee4ec9ec7bc8025f67793e2a6 100644 GIT binary patch delta 410 zcmaF3l=1v>#tkMCjNzM2B{CQVmhDw{x~KnQ9|IT!GjEV9>+O zPo*T71Pl$>U8^5Nut1e&PW~#RAYie5`3HySwRfR{-IIA`8#LS>Cvp0B90-OAuKf=M z9@Upa4!re+iMvm3mCX$%|9|lS delta 59 zcmV-B0L1^#rvc%m0kAX}0Z_9w7-#{rwHZ+XvmP4{0+U4@7?X7!c#|C-V6&AT(g6Yo Rk+C887qKvi5VJu@pbBX66mS3l diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 06bcff59a652a195edd84d8b91bec88354f003ce..cf9282eee2a3ea3fc878aa2dbc3b85ce631c41db 100644 GIT binary patch literal 17 VcmZQ(PG7Ze^~s_h1~6cp0su4&1l<4t literal 17 VcmZQ(PG7Ze^~s_h1~6cB001+%1c?9u diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index 2037b03cd40a019ddb04bbbb0d6ea81296790f97..7e81e5afce3ab3293290bbd69160c61dda662982 100644 GIT binary patch literal 8 PcmZQzV4Ni~^K&!+2W$eD literal 8 PcmZQzV4NlVWQHvO2Xz8y diff --git a/distribution-service/src/main/java/com/kt/distribution/config/OpenApiConfig.java b/distribution-service/src/main/java/com/kt/distribution/config/OpenApiConfig.java new file mode 100644 index 0000000..60c28ba --- /dev/null +++ b/distribution-service/src/main/java/com/kt/distribution/config/OpenApiConfig.java @@ -0,0 +1,52 @@ +package com.kt.distribution.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * OpenAPI (Swagger) Configuration + * Swagger UI 설정 및 API 문서화 + * + * @author System Architect + * @since 2025-10-24 + */ +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("Distribution Service API") + .description(""" + KT AI 기반 소상공인 이벤트 자동 생성 서비스의 다중 채널 배포 관리 API + + ## 주요 기능 + - 다중 채널 동시 배포 (우리동네TV, 링고비즈, 지니TV, SNS) + - 배포 상태 실시간 모니터링 + - Circuit Breaker 기반 장애 격리 + - Retry 패턴 및 Fallback 처리 + """) + .version("1.0.0") + .contact(new Contact() + .name("Digital Garage Team") + .email("support@kt-event-marketing.com"))) + .servers(List.of( + new Server() + .url("http://localhost:8085") + .description("Local Development Server"), + new Server() + .url("https://dev-api.kt-event-marketing.com/distribution/v1") + .description("Development Server"), + new Server() + .url("https://api.kt-event-marketing.com/distribution/v1") + .description("Production Server") + )); + } +} diff --git a/distribution-service/src/main/java/com/kt/distribution/controller/DistributionController.java b/distribution-service/src/main/java/com/kt/distribution/controller/DistributionController.java index d0825fb..aa0ed3e 100644 --- a/distribution-service/src/main/java/com/kt/distribution/controller/DistributionController.java +++ b/distribution-service/src/main/java/com/kt/distribution/controller/DistributionController.java @@ -4,6 +4,13 @@ import com.kt.distribution.dto.DistributionRequest; import com.kt.distribution.dto.DistributionResponse; import com.kt.distribution.dto.DistributionStatusResponse; import com.kt.distribution.service.DistributionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -11,8 +18,8 @@ import org.springframework.web.bind.annotation.*; /** * Distribution Controller - * POST /api/distribution/distribute - 다중 채널 배포 실행 - * GET /api/distribution/{eventId}/status - 배포 상태 조회 + * POST /distribution/distribute - 다중 채널 배포 실행 + * GET /distribution/{eventId}/status - 배포 상태 조회 * * @author System Architect * @since 2025-10-23 @@ -21,6 +28,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/distribution") @RequiredArgsConstructor +@Tag(name = "Distribution", description = "다중 채널 배포 관리 API") public class DistributionController { private final DistributionService distributionService; @@ -32,6 +40,29 @@ public class DistributionController { * @param request DistributionRequest * @return DistributionResponse */ + @Operation( + summary = "다중 채널 배포 요청", + description = """ + 이벤트 콘텐츠를 선택된 채널들에 동시 배포합니다. + + ## 처리 흐름 + 1. 배포 요청 검증 (이벤트 ID, 채널 목록, 콘텐츠 데이터) + 2. 채널별 병렬 배포 실행 (1분 이내 완료 목표) + 3. Circuit Breaker로 장애 채널 격리 + 4. 실패 시 Retry (지수 백오프: 1s, 2s, 4s) + 5. Fallback: 실패 채널 스킵 및 알림 + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "배포 완료", + content = @Content(schema = @Schema(implementation = DistributionResponse.class)) + ), + @ApiResponse(responseCode = "400", description = "잘못된 요청"), + @ApiResponse(responseCode = "404", description = "이벤트를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 내부 오류") + }) @PostMapping("/distribute") public ResponseEntity distribute(@RequestBody DistributionRequest request) { log.info("Received distribution request: eventId={}, channels={}", @@ -53,8 +84,30 @@ public class DistributionController { * @param eventId 이벤트 ID * @return DistributionStatusResponse */ + @Operation( + summary = "배포 상태 조회", + description = """ + 특정 이벤트의 배포 상태를 실시간으로 조회합니다. + + ## 조회 정보 + - 전체 배포 상태 (진행중, 완료, 부분성공, 실패) + - 채널별 배포 상태 및 결과 + - 실패 채널 상세 정보 (오류 유형, 재시도 횟수) + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "배포 상태 조회 성공", + content = @Content(schema = @Schema(implementation = DistributionStatusResponse.class)) + ), + @ApiResponse(responseCode = "404", description = "배포 이력을 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 내부 오류") + }) @GetMapping("/{eventId}/status") - public ResponseEntity getDistributionStatus(@PathVariable String eventId) { + public ResponseEntity getDistributionStatus( + @Parameter(description = "이벤트 ID", required = true, example = "evt-12345") + @PathVariable String eventId) { log.info("Received distribution status request: eventId={}", eventId); DistributionStatusResponse response = distributionService.getDistributionStatus(eventId); diff --git a/distribution-service/src/main/resources/application.yml b/distribution-service/src/main/resources/application.yml index 40fe36a..d6c5e99 100644 --- a/distribution-service/src/main/resources/application.yml +++ b/distribution-service/src/main/resources/application.yml @@ -119,6 +119,19 @@ channel: url: ${KAKAO_API_URL:http://localhost:9006/api/kakao} timeout: 10000 +# Springdoc OpenAPI (Swagger) +springdoc: + api-docs: + path: /v3/api-docs + enabled: true + swagger-ui: + path: /swagger-ui.html + enabled: true + operations-sorter: alpha + tags-sorter: alpha + display-request-duration: true + show-actuator: true + # Logging logging: file: @@ -132,3 +145,4 @@ logging: com.kt.distribution: DEBUG org.springframework.kafka: INFO io.github.resilience4j: DEBUG + org.springframework.web: DEBUG