├── .DS_Store ├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hexagonal-architecture ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── hexagonal │ │ │ ├── HexagonalApplication.java │ │ │ └── orderapp │ │ │ ├── adapter │ │ │ ├── in │ │ │ │ └── presentation │ │ │ │ │ ├── AdminReceiptController.java │ │ │ │ │ ├── PhoneOrderController.java │ │ │ │ │ ├── PhoneOrderResult.java │ │ │ │ │ └── WebOrderController.java │ │ │ └── out │ │ │ │ └── infrastructure │ │ │ │ └── persistence │ │ │ │ ├── OrderRecordEntity.java │ │ │ │ ├── OrderRecordRepository.java │ │ │ │ └── RecordOrderAdapter.java │ │ │ ├── application │ │ │ └── order │ │ │ │ ├── port │ │ │ │ ├── in │ │ │ │ │ ├── GetReceiptUseCase.java │ │ │ │ │ ├── OrderRequest.java │ │ │ │ │ ├── OrderResult.java │ │ │ │ │ ├── PlaceOrderUseCase.java │ │ │ │ │ └── ReceiptResult.java │ │ │ │ └── out │ │ │ │ │ ├── GetOrderRecordPort.java │ │ │ │ │ ├── OrderRecord.java │ │ │ │ │ └── RecordOrderPort.java │ │ │ │ └── service │ │ │ │ ├── GetReceiptService.java │ │ │ │ └── PlaceOrderService.java │ │ │ └── domain │ │ │ └── order │ │ │ ├── Order.java │ │ │ └── Receipt.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── hexagonal │ └── HexagonalApplicationTests.java ├── reactive ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── reactive │ │ │ ├── ReactiveAppApplication.java │ │ │ ├── controller │ │ │ └── DemoController.java │ │ │ └── functional │ │ │ └── DemoHandler.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── reactive │ └── ReactiveAppApplicationTests.java ├── settings.gradle ├── spring-batch ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── batch │ │ │ ├── BatchApplication.java │ │ │ └── job │ │ │ ├── DailyJobTimestamper.java │ │ │ ├── HelloWorldConfig.java │ │ │ ├── JobLoggerListener.java │ │ │ └── ParameterValidator.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── batch │ └── BatchApplicationTests.java ├── spring-boot-json-practice ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── json │ │ │ ├── JsonBootApplication.java │ │ │ └── demo │ │ │ ├── Demo.java │ │ │ ├── DemoApi.java │ │ │ └── WebConfig.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── json │ └── demo │ └── DemoApiTest.java ├── spring-cloud ├── demo │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ ├── DemoApplication.java │ │ │ ├── DemoController.java │ │ │ └── DemoService.java │ │ └── resources │ │ └── application.yml ├── eureka │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── EurekaApplication.java │ │ └── resources │ │ └── application.yml ├── gateway │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ ├── DemoFilter.java │ │ │ ├── GatewayApplication.java │ │ │ └── GlobalFilter.java │ │ └── resources │ │ └── application.yml └── temp │ ├── build.gradle │ └── src │ └── main │ ├── java │ └── com │ │ └── zkdlu │ │ ├── TempApplication.java │ │ └── TempController.java │ └── resources │ └── application.yml ├── spring-event ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── event │ │ │ ├── EventApplication.java │ │ │ ├── infra │ │ │ └── sms │ │ │ │ └── SmsClient.java │ │ │ └── order │ │ │ ├── domain │ │ │ ├── Order.java │ │ │ ├── OrderRepository.java │ │ │ └── OrderStatus.java │ │ │ ├── event │ │ │ └── OrderEvent.java │ │ │ └── service │ │ │ ├── OrderEventHandler.java │ │ │ ├── OrderRequest.java │ │ │ └── OrderService.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── event │ └── EventApplicationTests.java ├── spring-feign-test ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── zkdlu │ │ └── feign │ │ ├── FeignApplication.java │ │ ├── demo │ │ └── DemoApi.java │ │ └── source │ │ ├── Demo.java │ │ ├── DemoClient.java │ │ ├── DemoClientFallback.java │ │ └── EntryApi.java │ └── resources │ └── application.yml ├── spring-kafka ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── kafka │ │ │ ├── KafkaApplication.java │ │ │ ├── api │ │ │ └── TestApi.java │ │ │ ├── config │ │ │ └── KafkaConfig.java │ │ │ └── service │ │ │ ├── Consumer.java │ │ │ └── Producer.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── kafka │ └── KafkaApplicationTests.java ├── spring-msa-transaction ├── service-order │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── order │ │ │ ├── OrderApplication.java │ │ │ ├── api │ │ │ ├── Cart.java │ │ │ ├── CartItem.java │ │ │ ├── OrderController.java │ │ │ └── OrderMapper.java │ │ │ ├── domain │ │ │ ├── Order.java │ │ │ └── OrderLineItem.java │ │ │ └── service │ │ │ ├── OrderService.java │ │ │ ├── PayRequest.java │ │ │ └── remote │ │ │ └── PayReady.java │ │ └── resources │ │ └── application.yml ├── service-payment │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── zkdlu │ │ │ │ └── payment │ │ │ │ ├── PaymentApplication.java │ │ │ │ ├── api │ │ │ │ └── PayController.java │ │ │ │ ├── domain │ │ │ │ ├── Payment.java │ │ │ │ ├── PaymentRepository.java │ │ │ │ └── PaymentRepositoryImpl.java │ │ │ │ └── service │ │ │ │ ├── KakaoPay.java │ │ │ │ ├── PayService.java │ │ │ │ └── remote │ │ │ │ ├── Order.java │ │ │ │ ├── OrderLineItem.java │ │ │ │ ├── PayReady.java │ │ │ │ └── Product.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── com │ │ └── zkdlu │ │ └── payment │ │ ├── domain │ │ └── PaymentTest.java │ │ └── service │ │ └── KakaoPayTest.java ├── service-product │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── zkdlu │ │ │ │ └── product │ │ │ │ ├── ProductApplication.java │ │ │ │ ├── api │ │ │ │ └── ProductApi.java │ │ │ │ ├── domain │ │ │ │ ├── Product.java │ │ │ │ ├── ProductRepository.java │ │ │ │ └── ProductRepositoryImpl.java │ │ │ │ └── service │ │ │ │ └── ProductService.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── com │ │ └── zkdlu │ │ └── product │ │ └── service │ │ └── ProductServiceTest.java └── web │ ├── build.gradle │ └── src │ └── main │ ├── java │ └── com │ │ └── zkdlu │ │ ├── WebApplication.java │ │ └── web │ │ └── WebController.java │ └── resources │ ├── application.yml │ ├── static │ └── images │ │ └── payment_icon_yellow_large.png │ └── templates │ ├── index.html │ └── order.html ├── spring-oauth-jwt ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── oauthapp │ │ │ ├── OauthappApplication.java │ │ │ ├── api │ │ │ ├── TestApi.java │ │ │ └── TokenController.java │ │ │ ├── config │ │ │ ├── JwtAuthFilter.java │ │ │ ├── OAuth2SuccessHandler.java │ │ │ ├── SecurityConfig.java │ │ │ ├── UserDto.java │ │ │ └── UserRequestMapper.java │ │ │ └── service │ │ │ ├── CustomOAuth2UserService.java │ │ │ ├── OAuth2Attribute.java │ │ │ ├── Token.java │ │ │ └── TokenService.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── oauthapp │ └── OauthappApplicationTests.java ├── spring-object-binding ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ ├── BindingApplication.java │ │ │ └── binding │ │ │ ├── BodyData.java │ │ │ ├── DemoApi.java │ │ │ ├── DemoService.java │ │ │ ├── DemoServiceImpl.java │ │ │ ├── ModelAttributeData.java │ │ │ ├── MyFilter.java │ │ │ └── feign │ │ │ ├── FeignConfig.java │ │ │ ├── FeignService.java │ │ │ └── MyClient.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── binding │ ├── DemoApiTest.java │ └── SpyDemoService.java ├── spring-payment ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── zkdlu │ │ ├── KakaoPayApplication.java │ │ ├── advice │ │ └── ExceptionAdvice.java │ │ ├── api │ │ ├── order │ │ │ ├── Cart.java │ │ │ ├── CartItem.java │ │ │ └── OrderApi.java │ │ ├── payment │ │ │ └── PaymentApi.java │ │ └── product │ │ │ └── ProductApi.java │ │ ├── domain │ │ ├── order │ │ │ ├── Order.java │ │ │ ├── OrderEvent.java │ │ │ ├── OrderEventHandler.java │ │ │ ├── OrderItem.java │ │ │ ├── OrderRepository.java │ │ │ ├── OrderService.java │ │ │ └── PayRequest.java │ │ ├── payment │ │ │ ├── KakaoPay.java │ │ │ ├── KakaoPayReadyVO.java │ │ │ ├── Payment.java │ │ │ ├── PaymentRepository.java │ │ │ └── remote │ │ │ │ └── PayReady.java │ │ └── product │ │ │ ├── Product.java │ │ │ ├── ProductRepository.java │ │ │ └── ProductService.java │ │ └── web │ │ └── WebController.java │ └── resources │ ├── application.yml │ ├── static │ └── images │ │ └── payment_icon_yellow_large.png │ └── templates │ ├── index.html │ └── order.html ├── spring-querydsl ├── build.gradle └── src │ ├── main │ ├── generated │ │ └── com │ │ │ └── zkdlu │ │ │ └── querydsl │ │ │ └── domain │ │ │ └── QPerson.java │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── querydsl │ │ │ ├── QuerydslApplication.java │ │ │ ├── config │ │ │ └── QuerydslConfiguration.java │ │ │ └── domain │ │ │ ├── Person.java │ │ │ ├── PersonQueryRepository.java │ │ │ └── PersonRepository.java │ └── resources │ │ └── application.yml │ └── test │ ├── java │ └── com │ │ └── zkdlu │ │ └── querydsl │ │ ├── QuerydslApplicationTests.java │ │ └── domain │ │ └── PersonQueryRepositoryTest.java │ └── resources │ └── application.yml ├── spring-scheduled ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── scheduled │ │ │ ├── ScheduledApplication.java │ │ │ └── Scheduler.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── zkdlu │ └── scheduled │ ├── ScheduledApplicationTests.java │ └── SchedulerTest.java ├── spring-service ├── display-service │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── display │ │ │ ├── DisplayApplication.java │ │ │ ├── DisplayController.java │ │ │ ├── DisplayService.java │ │ │ ├── FeignProductService.java │ │ │ ├── FeignProductServiceFallbackImpl.java │ │ │ ├── KafkaProducerConfig.java │ │ │ ├── Producer.java │ │ │ └── Product.java │ │ └── resources │ │ └── application.yml ├── eureka-server │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── eureka │ │ │ └── EurekaApplication.java │ │ └── resources │ │ └── application.yml └── product-service │ ├── build.gradle │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── zkdlu │ │ │ └── product │ │ │ ├── Consumer.java │ │ │ ├── KafkaConsumerConfig.java │ │ │ ├── Product.java │ │ │ ├── ProductApi.java │ │ │ ├── ProductApplication.java │ │ │ ├── ProductRepository.java │ │ │ └── ProductService.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── zkdlu │ └── product │ └── ProductTest.java ├── spring-slack ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── zkdlu │ │ └── slacker │ │ ├── SlackApi.java │ │ ├── SlackBot.java │ │ ├── SlackService.java │ │ ├── SlackerApplication.java │ │ ├── TestApi.java │ │ └── Vote.java │ └── test │ └── java │ └── com │ └── zkdlu │ └── slacker │ └── SlackerApplicationTests.java └── spring-tdd ├── build.gradle └── src ├── main ├── java │ └── com │ │ └── zkdlu │ │ └── tdd │ │ ├── TddApplication.java │ │ ├── api │ │ └── OrderApi.java │ │ ├── domain │ │ ├── order │ │ │ ├── Order.java │ │ │ ├── OrderItem.java │ │ │ └── OrderRepository.java │ │ └── shop │ │ │ ├── Menu.java │ │ │ ├── Shop.java │ │ │ └── ShopRepository.java │ │ └── service │ │ ├── OrderDto.java │ │ └── OrderService.java └── resources │ ├── application.yml │ ├── data.sql │ └── schema.sql └── test └── java └── com └── zkdlu └── tdd ├── api └── OrderApiTest.java ├── domain └── order │ └── OrderTest.java └── service └── OrderServiceTest.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkdlu/spring-boot/6790c634fcd147659a856b4bf5b764af387a700a/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ test ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | env: 14 | working-directory: ./spring-tdd 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 11 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '11' 21 | distribution: 'adopt' 22 | - name: Grant execute permission for gradlew 23 | run: chmod +x gradlew 24 | working-directory: ${{ env.working-directory }} 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | working-directory: ${{ env.working-directory }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | HELP.md 3 | .gradle 4 | build/ 5 | !gradle/wrapper/gradle-wrapper.jar 6 | !**/src/main/**/build/ 7 | !**/src/test/**/build/ 8 | 9 | spring-slack/src/main/resources/application.yml 10 | 11 | ### STS ### 12 | .apt_generated 13 | .classpath 14 | .factorypath 15 | .project 16 | .settings 17 | .springBeans 18 | .sts4-cache 19 | bin/ 20 | !**/src/main/**/bin/ 21 | !**/src/test/**/bin/ 22 | 23 | ### IntelliJ IDEA ### 24 | .idea 25 | *.iws 26 | *.iml 27 | *.ipr 28 | out/ 29 | !**/src/main/**/out/ 30 | !**/src/test/**/out/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Spring 2 | 3 | 1. [헥사고날 아키텍처](https://zkdlu.tistory.com/4) 4 | 2. [Spring 스케줄러](https://zkdlu.github.io/2021-03-31/Spring-Scheduler/) 5 | 3. [Spring 이벤트](https://zkdlu.github.io/2021-03-29/Spring-Application-Event/) 6 | 4. [Test Code](https://zkdlu.tistory.com/8) 7 | 5. [QueryDSL](https://zkdlu.github.io/2021-04-13/Jpa03-QueryDSL/) 8 | 6. [OAuth2 + JWT + Security](https://zkdlu.tistory.com/12) 9 | 7. [MSA-API Gateway](https://zkdlu.tistory.com/6) 10 | 8. [MSA-Service Mesh](https://zkdlu.tistory.com/13) 11 | 9. [MSA-Circuit Breaker](https://zkdlu.tistory.com/14) 12 | 10. [MSA-Load Balancer](https://zkdlu.tistory.com/15) 13 | 11. [MSA-Service Discovery](https://zkdlu.tistory.com/17) 14 | 12. [Slack 연동하기](https://zkdlu.github.io/2021-04-18/Spring-boot-Slack/) 15 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.5.6' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" 10 | } 11 | } 12 | 13 | 14 | subprojects { 15 | apply plugin: 'java' 16 | apply plugin: 'org.springframework.boot' 17 | apply plugin: 'io.spring.dependency-management' 18 | 19 | group 'com.zkdlu' 20 | version '1.0-SNAPSHOT' 21 | sourceCompatibility = '17' 22 | 23 | repositories { 24 | mavenCentral() 25 | } 26 | 27 | dependencies { 28 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 29 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 30 | } 31 | } 32 | 33 | test { 34 | useJUnitPlatform() 35 | } 36 | 37 | ext { 38 | set('springCloudVersion', "2020.0.3") 39 | } 40 | 41 | dependencyManagement { 42 | imports { 43 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkdlu/spring-boot/6790c634fcd147659a856b4bf5b764af387a700a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /hexagonal-architecture/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 4 | runtimeOnly 'com.h2database:h2' 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 8 | } -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/HexagonalApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HexagonalApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(HexagonalApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/in/presentation/AdminReceiptController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.in.presentation; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.GetReceiptUseCase; 4 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.ReceiptResult; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RequiredArgsConstructor 11 | @RestController 12 | public class AdminReceiptController { 13 | private final GetReceiptUseCase getReceiptUseCase; 14 | 15 | @GetMapping("/receipt/{orderId}") 16 | public ReceiptResult getReceipt(@PathVariable String orderId) { 17 | return getReceiptUseCase.getReceipt(orderId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/in/presentation/PhoneOrderController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.in.presentation; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.OrderRequest; 4 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.OrderResult; 5 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.PlaceOrderUseCase; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RequiredArgsConstructor 12 | @RestController 13 | public class PhoneOrderController { 14 | private final PlaceOrderUseCase placeOrderUseCase; 15 | 16 | @GetMapping("/phone/{money}") 17 | public PhoneOrderResult order(@PathVariable int money) { 18 | OrderRequest orderRequest = new OrderRequest(money); 19 | OrderResult orderResult= placeOrderUseCase.placeOrder(orderRequest); 20 | 21 | return new PhoneOrderResult(orderResult.getOrderId(), orderResult.getMoney()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/in/presentation/PhoneOrderResult.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.in.presentation; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | class PhoneOrderResult { 9 | private String orderId; 10 | private int price; 11 | } 12 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/in/presentation/WebOrderController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.in.presentation; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.OrderRequest; 4 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.OrderResult; 5 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.PlaceOrderUseCase; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RequiredArgsConstructor 12 | @RestController 13 | public class WebOrderController { 14 | private final PlaceOrderUseCase placeOrderUseCase; 15 | 16 | @GetMapping("/web/{money}") 17 | public OrderResult order(@PathVariable int money) { 18 | 19 | OrderRequest orderRequest = new OrderRequest(money); 20 | return placeOrderUseCase.placeOrder(orderRequest); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/out/infrastructure/persistence/OrderRecordEntity.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.out.infrastructure.persistence; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.OrderRecord; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | import javax.persistence.Table; 12 | 13 | @NoArgsConstructor 14 | @Getter 15 | @Entity 16 | @Table(name = "orderRecord") 17 | class OrderRecordEntity { 18 | @Id 19 | @GeneratedValue 20 | private long id; 21 | @Column(unique = true) 22 | private String orderId; 23 | private int money; 24 | 25 | private OrderRecordEntity(String orderId, int money) { 26 | this.orderId = orderId; 27 | this.money = money; 28 | } 29 | 30 | public static OrderRecordEntity from(OrderRecord orderRecord) { 31 | return new OrderRecordEntity(orderRecord.getOrderId(), orderRecord.getMoney()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/out/infrastructure/persistence/OrderRecordRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.out.infrastructure.persistence; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface OrderRecordRepository extends JpaRepository { 8 | public Optional findByOrderId(String orderId); 9 | } 10 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/adapter/out/infrastructure/persistence/RecordOrderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.adapter.out.infrastructure.persistence; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.GetOrderRecordPort; 4 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.OrderRecord; 5 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.RecordOrderPort; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @RequiredArgsConstructor 10 | @Repository 11 | public class RecordOrderAdapter implements RecordOrderPort, GetOrderRecordPort { 12 | private final OrderRecordRepository orderRecordRepository; 13 | 14 | @Override 15 | public void recordOrder(OrderRecord orderRecord) { 16 | orderRecordRepository.save(OrderRecordEntity.from(orderRecord)); 17 | } 18 | 19 | @Override 20 | public OrderRecord getOrder(String orderId) { 21 | OrderRecordEntity orderRecord = orderRecordRepository.findByOrderId(orderId).orElseThrow(IllegalAccessError::new); 22 | 23 | return new OrderRecord(orderRecord.getOrderId(), orderRecord.getMoney()); 24 | } 25 | } -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/in/GetReceiptUseCase.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.in; 2 | 3 | public interface GetReceiptUseCase { 4 | ReceiptResult getReceipt(String orderId); 5 | } 6 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/in/OrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.in; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class OrderRequest { 9 | private int money; 10 | } 11 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/in/OrderResult.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.in; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class OrderResult { 9 | private String orderId; 10 | private int money; 11 | } 12 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/in/PlaceOrderUseCase.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.in; 2 | 3 | public interface PlaceOrderUseCase { 4 | OrderResult placeOrder(OrderRequest orderDetail); 5 | } 6 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/in/ReceiptResult.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.in; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class ReceiptResult { 9 | private String orderId; 10 | private int money; 11 | } 12 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/out/GetOrderRecordPort.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.out; 2 | 3 | public interface GetOrderRecordPort { 4 | OrderRecord getOrder(String orderId); 5 | } -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/out/OrderRecord.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.out; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class OrderRecord { 9 | private String orderId; 10 | private int money; 11 | } 12 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/port/out/RecordOrderPort.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.port.out; 2 | 3 | public interface RecordOrderPort { 4 | void recordOrder(OrderRecord orderRecord); 5 | } 6 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/service/GetReceiptService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.service; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.GetReceiptUseCase; 4 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.ReceiptResult; 5 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.GetOrderRecordPort; 6 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.OrderRecord; 7 | import com.zkdlu.hexagonal.orderapp.domain.order.Receipt; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Service; 10 | 11 | @RequiredArgsConstructor 12 | @Service 13 | public class GetReceiptService implements GetReceiptUseCase { 14 | private final GetOrderRecordPort orderRecordPort; 15 | @Override 16 | public ReceiptResult getReceipt(String orderId) { 17 | OrderRecord orderRecord = orderRecordPort.getOrder(orderId); 18 | Receipt receipt = new Receipt(orderRecord.getOrderId(), orderRecord.getMoney()); 19 | 20 | return new ReceiptResult(receipt.getOrderId(), receipt.getMoney()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/application/order/service/PlaceOrderService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.application.order.service; 2 | 3 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.OrderRequest; 4 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.OrderResult; 5 | import com.zkdlu.hexagonal.orderapp.application.order.port.in.PlaceOrderUseCase; 6 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.OrderRecord; 7 | import com.zkdlu.hexagonal.orderapp.application.order.port.out.RecordOrderPort; 8 | import com.zkdlu.hexagonal.orderapp.domain.order.Order; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.UUID; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class PlaceOrderService implements PlaceOrderUseCase { 18 | private final RecordOrderPort recordOrderPort; 19 | 20 | @Transactional 21 | @Override 22 | public OrderResult placeOrder(OrderRequest orderDetail) { 23 | Order order = new Order(UUID.randomUUID().toString(), orderDetail.getMoney()); 24 | order.place(); 25 | 26 | recordOrderPort.recordOrder(new OrderRecord(order.getId(), order.getMoney())); 27 | 28 | return new OrderResult(order.getId(), order.getMoney()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/domain/order/Order.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.domain.order; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class Order { 7 | public enum OrderState { PREPARE, COMPLETE } 8 | 9 | private String id; 10 | private OrderState orderState; 11 | private int money; 12 | 13 | public Order(String id, int money) { 14 | this.id = id; 15 | this.orderState = OrderState.PREPARE; 16 | this.money = money; 17 | } 18 | 19 | public void place() { 20 | this.orderState = OrderState.COMPLETE; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/java/com/zkdlu/hexagonal/orderapp/domain/order/Receipt.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal.orderapp.domain.order; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class Receipt { 7 | private String orderId; 8 | private int money; 9 | 10 | public Receipt(String orderId, int money) { 11 | this.orderId = orderId; 12 | this.money = money; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hexagonal-architecture/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:h2:tcp://localhost/~/test 4 | driver-class-name: org.h2.Driver 5 | username: sa 6 | password: 7 | jpa: 8 | database-platform: org.hibernate.dialect.H2Dialect 9 | database: h2 10 | show-sql: true 11 | hibernate: 12 | ddl-auto: create -------------------------------------------------------------------------------- /hexagonal-architecture/src/test/java/com/zkdlu/hexagonal/HexagonalApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.hexagonal; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class HexagonalApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /reactive/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 3 | compileOnly 'org.projectlombok:lombok' 4 | annotationProcessor 'org.projectlombok:lombok' 5 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 6 | testImplementation 'io.projectreactor:reactor-test' 7 | } -------------------------------------------------------------------------------- /reactive/src/main/java/com/zkdlu/reactive/ReactiveAppApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.reactive; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ReactiveAppApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ReactiveAppApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /reactive/src/main/java/com/zkdlu/reactive/controller/DemoController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.reactive.controller; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.util.Collections; 13 | import java.util.Map; 14 | import java.util.stream.Stream; 15 | 16 | @RestController 17 | public class DemoController { 18 | @GetMapping 19 | Flux hello() { 20 | return Flux.just("Hello", " ", "World"); 21 | } 22 | 23 | @GetMapping("/stream") 24 | Flux> stream() { 25 | Stream stream = Stream.iterate(0, i -> i + 1); 26 | return Flux.fromStream(stream) 27 | .map(i -> Collections.singletonMap("value", i)); 28 | } 29 | 30 | @PostMapping("/echo") 31 | Mono echo(@RequestBody Mono body) { 32 | return body; 33 | } 34 | 35 | @NoArgsConstructor 36 | @Getter 37 | public static class Demo { 38 | private String id; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /reactive/src/main/java/com/zkdlu/reactive/functional/DemoHandler.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.reactive.functional; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.core.ParameterizedTypeReference; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.reactive.function.BodyInserters; 8 | import org.springframework.web.reactive.function.server.RequestPredicates; 9 | import org.springframework.web.reactive.function.server.RouterFunction; 10 | import org.springframework.web.reactive.function.server.RouterFunctions; 11 | import org.springframework.web.reactive.function.server.ServerRequest; 12 | import org.springframework.web.reactive.function.server.ServerResponse; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | 16 | import java.util.Collections; 17 | import java.util.Map; 18 | import java.util.stream.Stream; 19 | 20 | @Component 21 | public class DemoHandler { 22 | @Bean 23 | public RouterFunction routes() { 24 | return RouterFunctions.route( 25 | RequestPredicates.GET("/hello"), this::hello) 26 | .andRoute( 27 | RequestPredicates.GET("/stream-hello"), this::stream 28 | ); 29 | } 30 | 31 | public Mono hello(ServerRequest request) { 32 | return ServerResponse.ok() 33 | .body(Flux.just("Hello", " ", "World"), String.class); 34 | } 35 | 36 | public Mono stream(ServerRequest request) { 37 | Stream stream = Stream.iterate(0, i -> i + 1); 38 | Flux> flux = Flux.fromStream(stream) 39 | .map(i -> Collections.singletonMap("value", i)); 40 | 41 | return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) 42 | .body(BodyInserters.fromPublisher(flux, new ParameterizedTypeReference>() {})); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reactive/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /reactive/src/test/java/com/zkdlu/reactive/ReactiveAppApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.reactive; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ReactiveAppApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-boot' 2 | 3 | include(':hexagonal-architecture') 4 | include(':spring-batch') 5 | include(':reactive') 6 | include(':spring-boot-json-practice') 7 | //include(':spring-cloud:gateway', ':spring-cloud:demo', ':spring-cloud:temp', ':spring-cloud:eureka') 8 | include(':spring-event') 9 | include(':spring-kafka') 10 | include(':spring-msa-transaction:web', ':spring-msa-transaction:service-product', ':spring-msa-transaction:service-payment', ':spring-msa-transaction:service-order') 11 | include(':spring-oauth-jwt') 12 | include(':spring-payment') 13 | include(':spring-querydsl') 14 | include(':spring-scheduled') 15 | //include(':spring-service:product-service', ':spring-service:display-service', ':spring-service:eureka-server') 16 | include(':spring-slack') 17 | include(':spring-tdd') 18 | include(':spring-feign-test') 19 | include(':spring-object-binding') 20 | -------------------------------------------------------------------------------- /spring-batch/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-batch' 3 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 4 | implementation 'mysql:mysql-connector-java' 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 8 | testImplementation 'org.springframework.batch:spring-batch-test' 9 | } -------------------------------------------------------------------------------- /spring-batch/src/main/java/com/zkdlu/batch/BatchApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.batch; 2 | 3 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableBatchProcessing 8 | @SpringBootApplication 9 | public class BatchApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(BatchApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-batch/src/main/java/com/zkdlu/batch/job/DailyJobTimestamper.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.batch.job; 2 | 3 | import org.springframework.batch.core.JobParameters; 4 | import org.springframework.batch.core.JobParametersBuilder; 5 | import org.springframework.batch.core.JobParametersIncrementer; 6 | 7 | import java.util.Date; 8 | 9 | class DailyJobTimestamper implements JobParametersIncrementer { 10 | @Override 11 | public JobParameters getNext(JobParameters parameters) { 12 | return new JobParametersBuilder(parameters) 13 | .addDate("currentDate", new Date()) 14 | .toJobParameters(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-batch/src/main/java/com/zkdlu/batch/job/JobLoggerListener.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.batch.job; 2 | 3 | import org.springframework.batch.core.JobExecution; 4 | import org.springframework.batch.core.annotation.AfterJob; 5 | import org.springframework.batch.core.annotation.BeforeJob; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | class JobLoggerListener { 10 | 11 | private static final String START_MESSAGE = "%s is beginning execution"; 12 | private static final String END_MESSAGE = "%s has completed with the status %s"; 13 | 14 | 15 | @BeforeJob 16 | public void beforeJob(JobExecution jobExecution) { 17 | System.out.println(String.format(START_MESSAGE, jobExecution.getJobInstance().getJobName())); 18 | } 19 | 20 | @AfterJob 21 | public void afterJob(JobExecution jobExecution) { 22 | System.out.println(String.format(END_MESSAGE, 23 | jobExecution.getJobInstance().getJobName(), 24 | jobExecution.getStatus())); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-batch/src/main/java/com/zkdlu/batch/job/ParameterValidator.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.batch.job; 2 | 3 | import org.springframework.batch.core.JobParameters; 4 | import org.springframework.batch.core.JobParametersInvalidException; 5 | import org.springframework.batch.core.JobParametersValidator; 6 | import org.springframework.util.StringUtils; 7 | 8 | class ParameterValidator implements JobParametersValidator { 9 | @Override 10 | public void validate(JobParameters parameters) throws JobParametersInvalidException { 11 | String fileName = parameters.getString("fileName"); 12 | if (!StringUtils.hasText(fileName)) { 13 | throw new JobParametersInvalidException("fileName empty"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-batch/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.cj.jdbc.Driver 4 | url: jdbc:mysql://localhost:3306/spring_batch 5 | username: root 6 | password: root 7 | jpa: 8 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 9 | show-sql: true 10 | hibernate: 11 | format_sql: true 12 | ddl-auto: create -------------------------------------------------------------------------------- /spring-batch/src/test/java/com/zkdlu/batch/BatchApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.batch; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BatchApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-json-practice/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 4 | 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | } -------------------------------------------------------------------------------- /spring-boot-json-practice/src/main/java/com/zkdlu/json/JsonBootApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.json; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JsonBootApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(JsonBootApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-json-practice/src/main/java/com/zkdlu/json/demo/Demo.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.json.demo; 2 | 3 | public class Demo { 4 | private int intValue; 5 | private String stringValue; 6 | private boolean booleanValue; 7 | private float floatValue; 8 | 9 | public Demo(int intValue, String stringValue, boolean booleanValue, float floatValue) { 10 | this.intValue = intValue; 11 | this.stringValue = stringValue; 12 | this.booleanValue = booleanValue; 13 | this.floatValue = floatValue; 14 | } 15 | 16 | public int getIntValue() { 17 | return intValue; 18 | } 19 | 20 | public String getStringValue() { 21 | return stringValue; 22 | } 23 | 24 | public boolean hasBooleanValue() { 25 | return booleanValue; 26 | } 27 | 28 | public float getFloatValue() { 29 | return floatValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-json-practice/src/main/java/com/zkdlu/json/demo/DemoApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.json.demo; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class DemoApi { 8 | @GetMapping("/demo") 9 | public Demo getDemo() { 10 | return new Demo(1, "str", true, 1f); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-json-practice/src/main/java/com/zkdlu/json/demo/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.json.demo; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.converter.HttpMessageConverter; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 11 | 12 | import java.util.List; 13 | 14 | @Configuration 15 | @EnableWebMvc 16 | public class WebConfig implements WebMvcConfigurer { 17 | @Override 18 | public void configureMessageConverters(List> converters) { 19 | ObjectMapper objectMapper = new ObjectMapper() 20 | .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) 21 | .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 22 | 23 | var converter = new MappingJackson2HttpMessageConverter(); 24 | converter.setObjectMapper(objectMapper); 25 | 26 | converters.add(converter); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-json-practice/src/main/resources/application.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkdlu/spring-boot/6790c634fcd147659a856b4bf5b764af387a700a/spring-boot-json-practice/src/main/resources/application.yml -------------------------------------------------------------------------------- /spring-boot-json-practice/src/test/java/com/zkdlu/json/demo/DemoApiTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.json.demo; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 11 | 12 | import static org.hamcrest.Matchers.equalTo; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 17 | 18 | public class DemoApiTest { 19 | 20 | private MockMvc mockMvc; 21 | 22 | @BeforeEach 23 | void setUp() { 24 | DemoApi demoApi = new DemoApi(); 25 | ObjectMapper objectMapper = new ObjectMapper() 26 | .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) 27 | .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 28 | 29 | mockMvc = MockMvcBuilders.standaloneSetup(demoApi) 30 | .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) 31 | .build(); 32 | } 33 | 34 | @Test 35 | void getDemo_returns_okHttpStatus() throws Exception { 36 | mockMvc.perform(get("/demo")) 37 | .andExpect(status().isOk()); 38 | } 39 | 40 | @Test 41 | void getDemo_returns_a_singleDemo() throws Exception { 42 | mockMvc.perform(get("/demo")) 43 | .andDo(print()) 44 | .andExpect(jsonPath("$.intValue", equalTo(1))) 45 | .andExpect(jsonPath("$.stringValue", equalTo("str"))) 46 | .andExpect(jsonPath("$.floatValue", equalTo(1.0))) 47 | .andExpect(jsonPath("$.booleanValue", equalTo(true))); 48 | } 49 | } -------------------------------------------------------------------------------- /spring-cloud/demo/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 3 | implementation 'org.springframework.boot:spring-boot-starter-aop' 4 | implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j' 5 | implementation 'org.springframework.boot:spring-boot-starter-web' 6 | } 7 | -------------------------------------------------------------------------------- /spring-cloud/demo/src/main/java/com/zkdlu/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-cloud/demo/src/main/java/com/zkdlu/DemoController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import reactor.core.publisher.Mono; 7 | 8 | @RestController 9 | @RequestMapping("/demo") 10 | public class DemoController { 11 | private final DemoService demoService; 12 | 13 | public DemoController(DemoService demoService) { 14 | this.demoService = demoService; 15 | } 16 | 17 | @GetMapping("/success") 18 | public String success() { 19 | return demoService.success(); 20 | } 21 | 22 | @GetMapping("/fail") 23 | public String fail() { 24 | return demoService.fail(); 25 | } 26 | 27 | @GetMapping("/retry") 28 | public Mono retry() { 29 | return demoService.retry(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-cloud/demo/src/main/java/com/zkdlu/DemoService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 4 | import io.github.resilience4j.retry.annotation.Retry; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.HttpMethod; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | import reactor.core.publisher.Mono; 13 | 14 | import java.util.Collections; 15 | 16 | @Service 17 | public class DemoService { 18 | private static final Logger log = LoggerFactory.getLogger(DemoService.class); 19 | private static final String TEST_CIRCUIT_BREAKER = "testCircuitBreaker"; 20 | 21 | @CircuitBreaker(name = TEST_CIRCUIT_BREAKER, fallbackMethod = "helloFallback") 22 | public String success() { 23 | return "success"; 24 | } 25 | 26 | @CircuitBreaker(name = TEST_CIRCUIT_BREAKER, fallbackMethod = "helloFallback") 27 | public String fail() { 28 | throw new IllegalStateException("fail"); 29 | } 30 | 31 | @CircuitBreaker(name = TEST_CIRCUIT_BREAKER, fallbackMethod = "retryFallback") 32 | @Retry(name = TEST_CIRCUIT_BREAKER) 33 | public Mono retry() { 34 | log.info("retry"); 35 | WebClient webClient = WebClient 36 | .builder() 37 | .baseUrl("http://localhost:9000") 38 | .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 39 | .defaultUriVariables(Collections.singletonMap("url", "http://localhost:9000")) 40 | .build(); 41 | 42 | webClient.get(); 43 | 44 | return webClient 45 | .method(HttpMethod.GET) 46 | .uri("/") 47 | .retrieve() 48 | .bodyToMono(String.class); 49 | } 50 | 51 | public String helloFallback(Throwable t) { 52 | return "fallback invoked!: " + t.getClass(); 53 | } 54 | 55 | public Mono retryFallback(Throwable t) { 56 | return Mono.just("fallback invoked!: " + t.getClass()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spring-cloud/demo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | spring: 5 | application: 6 | name: demo 7 | 8 | eureka: 9 | instance: 10 | prefer-ip-address: true 11 | client: 12 | service-url: 13 | defaultZone: http://localhost:8761/eureka 14 | 15 | management: 16 | health: 17 | circuitbreakers: 18 | enabled: true 19 | 20 | resilience4j.circuitbreaker: 21 | configs: 22 | default: 23 | registerHealthIndicator: true 24 | # actuator를 통해 circuitbraker 상태를 확인하기 위해 설정 25 | minimumNumberOfCalls: 5 26 | # Circuit Breaker가 에러 비율 또 slow call 비율을 계산하기 전에 요구되는 최소한의 요청 수 27 | failureRateThreshold: 50 28 | # 에러 비율 (퍼센트)로 해당 값 이상으로 에러 발생시 서킷이 Open 된다. 29 | waitDurationInOpenState: 10s 30 | # 서킷의 상태가 Open에서 Half-open으로 변경되기 전에 Circuit Breaker가 기다리는 시간 31 | instances: 32 | testCircuitBreaker: 33 | baseConfig: default 34 | 35 | resilience4j.bulkhead: 36 | instances: 37 | testCircuitBreaker: 38 | maxConcurrentCalls: 25 39 | # 허가된 동시 실행 수를 25로 지정 40 | maxWaitDuration: 0 41 | # 포화 상태의 Bulkhead에 진입하기 위해 block 되는 최대 시간, 값이 0이므로 바로 요청을 막는다. 42 | 43 | resilience4j.ratelimiter: 44 | instances: 45 | testCircuitBreaker: 46 | limitForPeriod: 50 47 | # limitRefreshPeriod 기간 동안 허용되는 요청 수 48 | limitRefreshPeriod: 500ns 49 | # limit refresh 기간 50 | timeoutDuration: 5s 51 | # 허가를 위해 쓰레드가 대기하는 기본 시간 52 | registerHealthIndicator: true 53 | 54 | # fallback method가 정의되어있지 않은 에러의 경우에만 재시도 한다. 55 | resilience4j.retry: 56 | instances: 57 | testCircuitBreaker: 58 | maxRetryAttempts: 5 59 | # 최대 재시도 수 60 | waitDuration: 10000 61 | # 재시도 사이에 고정된 시간 62 | retryExceptions: 63 | # Empty 일 경우 모든 에러 클래스에 대해 재시도 64 | # - org.springframework.web.client.HttpServerErrorException 65 | # - io.github.resilience4j.circuitbreaker.Exception 66 | 67 | resilience4j.timelimiter: 68 | instances: 69 | testCircuitBreaker: 70 | timeoutDuration: 1s 71 | # 원격 서버로부터 해당 시간안에 응답이 오는 것을 제한 72 | -------------------------------------------------------------------------------- /spring-cloud/eureka/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' 3 | } 4 | -------------------------------------------------------------------------------- /spring-cloud/eureka/src/main/java/com/zkdlu/EurekaApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class EurekaApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(EurekaApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-cloud/eureka/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | spring: 5 | application: 6 | name: eureka-server 7 | 8 | eureka: 9 | server: 10 | response-cache-update-interval-ms: 1000 # Default 30,000ms 11 | client: 12 | register-with-eureka: false # Only for local stand-alone development 13 | fetch-registry: false # Only for local stand-alone development 14 | service-url: 15 | defaultZone: http://localhost:8761/eureka # Default Value. Just for demo -------------------------------------------------------------------------------- /spring-cloud/gateway/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 3 | implementation 'org.springframework.cloud:spring-cloud-starter-gateway' 4 | } -------------------------------------------------------------------------------- /spring-cloud/gateway/src/main/java/com/zkdlu/DemoFilter.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.cloud.gateway.filter.GatewayFilter; 6 | import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; 7 | import org.springframework.stereotype.Component; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Component 11 | public class DemoFilter extends AbstractGatewayFilterFactory { 12 | private final Logger log = LoggerFactory.getLogger(DemoFilter.class); 13 | 14 | public DemoFilter() { 15 | super(Config.class); 16 | } 17 | 18 | @Override 19 | public GatewayFilter apply(Config config) { 20 | return ((exchange, chain) -> { 21 | log.info("DemoFilter baseMessage>>>>>>" + config.getBaseMessage()); 22 | if (config.isPreLogger()) { 23 | log.info("DemoFilter Start>>>>>>" + exchange.getRequest()); 24 | } 25 | return chain.filter(exchange).then(Mono.fromRunnable(()->{ 26 | if (config.isPostLogger()) { 27 | log.info("DemoFilter End>>>>>>" + exchange.getResponse()); 28 | } 29 | })); 30 | }); 31 | } 32 | 33 | public static class Config { 34 | private String baseMessage; 35 | private boolean preLogger; 36 | private boolean postLogger; 37 | 38 | public String getBaseMessage() { 39 | return baseMessage; 40 | } 41 | 42 | public void setBaseMessage(String baseMessage) { 43 | this.baseMessage = baseMessage; 44 | } 45 | 46 | public boolean isPreLogger() { 47 | return preLogger; 48 | } 49 | 50 | public void setPreLogger(boolean preLogger) { 51 | this.preLogger = preLogger; 52 | } 53 | 54 | public boolean isPostLogger() { 55 | return postLogger; 56 | } 57 | 58 | public void setPostLogger(boolean postLogger) { 59 | this.postLogger = postLogger; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spring-cloud/gateway/src/main/java/com/zkdlu/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class GatewayApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(GatewayApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-cloud/gateway/src/main/java/com/zkdlu/GlobalFilter.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.cloud.gateway.filter.GatewayFilter; 6 | import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; 7 | import org.springframework.stereotype.Component; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Component 11 | public class GlobalFilter extends AbstractGatewayFilterFactory { 12 | private final Logger log = LoggerFactory.getLogger(GlobalFilter.class); 13 | 14 | public GlobalFilter() { 15 | super(Config.class); 16 | } 17 | 18 | @Override 19 | public GatewayFilter apply(Config config) { 20 | return ((exchange, chain) -> { 21 | log.info("GlobalFilter baseMessage>>>>>>" + config.getBaseMessage()); 22 | if (config.isPreLogger()) { 23 | log.info("GlobalFilter Start>>>>>>" + exchange.getRequest()); 24 | } 25 | return chain.filter(exchange).then(Mono.fromRunnable(()->{ 26 | if (config.isPostLogger()) { 27 | log.info("GlobalFilter End>>>>>>" + exchange.getResponse()); 28 | } 29 | })); 30 | }); 31 | } 32 | 33 | public static class Config { 34 | private String baseMessage; 35 | private boolean preLogger; 36 | private boolean postLogger; 37 | 38 | public String getBaseMessage() { 39 | return baseMessage; 40 | } 41 | 42 | public void setBaseMessage(String baseMessage) { 43 | this.baseMessage = baseMessage; 44 | } 45 | 46 | public boolean isPreLogger() { 47 | return preLogger; 48 | } 49 | 50 | public void setPreLogger(boolean preLogger) { 51 | this.preLogger = preLogger; 52 | } 53 | 54 | public boolean isPostLogger() { 55 | return postLogger; 56 | } 57 | 58 | public void setPostLogger(boolean postLogger) { 59 | this.postLogger = postLogger; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spring-cloud/gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | application: 4 | name: gateway 5 | cloud: 6 | gateway: 7 | default-filters: 8 | - name: GlobalFilter 9 | args: 10 | baseMessage: Gateway GlobalFilter 11 | preLogger: true 12 | postLogger: true 13 | routes: 14 | - id: demo 15 | uri: lb://demo 16 | predicates: Path=/demo/** 17 | filters: 18 | - name: DemoFilter 19 | args: 20 | baseMessage: DemoFilter 21 | preLogger: true 22 | postLogger: true 23 | 24 | eureka: 25 | instance: 26 | prefer-ip-address: true 27 | client: 28 | service-url: 29 | defaultZone: http://localhost:8761/eureka -------------------------------------------------------------------------------- /spring-cloud/temp/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | } 4 | -------------------------------------------------------------------------------- /spring-cloud/temp/src/main/java/com/zkdlu/TempApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TempApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(TempApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-cloud/temp/src/main/java/com/zkdlu/TempController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class TempController { 8 | @GetMapping 9 | public String temp() { 10 | return "temp"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-cloud/temp/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 -------------------------------------------------------------------------------- /spring-event/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter' 3 | compileOnly 'org.projectlombok:lombok' 4 | annotationProcessor 'org.projectlombok:lombok' 5 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 6 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 7 | } 8 | } -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/EventApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event; 2 | 3 | import com.zkdlu.event.order.service.OrderRequest; 4 | import com.zkdlu.event.order.service.OrderService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.ApplicationArguments; 7 | import org.springframework.boot.ApplicationRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | 11 | @SpringBootApplication 12 | public class EventApplication implements ApplicationRunner { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(EventApplication.class, args); 16 | } 17 | 18 | @Autowired 19 | private OrderService orderService; 20 | 21 | @Override 22 | public void run(ApplicationArguments args) throws Exception { 23 | OrderRequest orderRequest = new OrderRequest(1L); 24 | 25 | orderService.placeOrder(orderRequest); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/infra/sms/SmsClient.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.infra.sms; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Slf4j 7 | @Component 8 | public class SmsClient { 9 | public void send(String msg) { 10 | log.info("메시지를 보냅니다.: {}", msg); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.domain; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | @Slf4j 6 | public class Order { 7 | private long orderId; 8 | private OrderStatus orderStatus; 9 | 10 | public Order(long orderId) { 11 | this.orderId = orderId; 12 | } 13 | 14 | public void place() { 15 | this.orderStatus = OrderStatus.PAYMENT_WAITING; 16 | } 17 | 18 | public void payed() { 19 | this.orderStatus = OrderStatus.PAYED; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/domain/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.domain; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import java.util.Optional; 6 | 7 | @Repository 8 | public class OrderRepository { 9 | 10 | public Optional findById(long orderId) { 11 | return Optional.of(new Order(orderId)); 12 | } 13 | 14 | public Order save(Order order) { 15 | return order; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/domain/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.domain; 2 | 3 | public enum OrderStatus { 4 | PAYMENT_WAITING("걸제 대기중"), 5 | PAYED("결제 완료"); 6 | 7 | private String description; 8 | 9 | OrderStatus(String description) { this.description = description; } 10 | 11 | public String getDescription() { 12 | return description; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/event/OrderEvent.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.event; 2 | 3 | import lombok.Getter; 4 | import org.springframework.context.ApplicationEvent; 5 | 6 | @Getter 7 | public class OrderEvent extends ApplicationEvent { 8 | private long orderId; 9 | 10 | public OrderEvent(Object source, long orderId) { 11 | super(source); 12 | 13 | this.orderId = orderId; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/service/OrderEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.service; 2 | 3 | import com.zkdlu.event.infra.sms.SmsClient; 4 | import com.zkdlu.event.order.event.OrderEvent; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.context.event.EventListener; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @RequiredArgsConstructor 12 | @Component 13 | public class OrderEventHandler { 14 | private final SmsClient smsClient; 15 | 16 | @EventListener 17 | public void send(OrderEvent orderEvent) throws InterruptedException { 18 | log.info("메시지를 보냅니다. 1 {}", Thread.currentThread().toString()); 19 | } 20 | } 21 | 22 | @Slf4j 23 | @Component 24 | class OrderEventHandler2 { 25 | @EventListener 26 | public void foo(OrderEvent orderEvent) { 27 | log.info("메시지를 보냅니다. 2 {}", Thread.currentThread().toString()); 28 | } 29 | } -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/service/OrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.service; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class OrderRequest { 7 | private long orderId; 8 | 9 | public OrderRequest(long orderId) { 10 | this.orderId = orderId; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-event/src/main/java/com/zkdlu/event/order/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event.order.service; 2 | 3 | import com.zkdlu.event.order.domain.Order; 4 | import com.zkdlu.event.order.domain.OrderRepository; 5 | import com.zkdlu.event.order.event.OrderEvent; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.context.ApplicationEventPublisher; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Slf4j 12 | @RequiredArgsConstructor 13 | @Service 14 | public class OrderService { 15 | private final OrderRepository orderRepository; 16 | private final ApplicationEventPublisher eventPublisher; 17 | 18 | public void placeOrder(OrderRequest orderRequest) { 19 | Order order = new Order(orderRequest.getOrderId()); 20 | order.place(); 21 | orderRepository.save(order); 22 | 23 | log.info(Thread.currentThread().toString()); 24 | eventPublisher.publishEvent(new OrderEvent(this, orderRequest.getOrderId())); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-event/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spring-event/src/test/java/com/zkdlu/event/EventApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.event; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class EventApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-feign-test/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | compileOnly 'org.projectlombok:lombok' 4 | annotationProcessor 'org.projectlombok:lombok' 5 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 6 | 7 | implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' 8 | implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j' 9 | } -------------------------------------------------------------------------------- /spring-feign-test/src/main/java/com/zkdlu/feign/FeignApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.feign; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | 7 | @EnableFeignClients 8 | @SpringBootApplication 9 | public class FeignApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(FeignApplication.class, args); 12 | } 13 | } -------------------------------------------------------------------------------- /spring-feign-test/src/main/java/com/zkdlu/feign/demo/DemoApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.feign.demo; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | @RestController 10 | public class DemoApi { 11 | @GetMapping("/demo1") 12 | public Map demo1() { 13 | Map map = new HashMap(); 14 | map.put("content", "demo"); 15 | 16 | return map; 17 | } 18 | 19 | @GetMapping("/demo2") 20 | public Map demo2() { 21 | Map map = new HashMap(); 22 | map.put("test", "test"); 23 | 24 | return map; 25 | } 26 | 27 | @GetMapping("/demo3") 28 | public Map demo3() { 29 | throw new RuntimeException(); 30 | } 31 | 32 | @GetMapping("/demo4") 33 | public Map demo4() { 34 | Map map = new HashMap(); 35 | 36 | try { 37 | Thread.sleep(5000); 38 | } catch (InterruptedException e) { 39 | e.printStackTrace(); 40 | } 41 | 42 | return map; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spring-feign-test/src/main/java/com/zkdlu/feign/source/Demo.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.feign.source; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public class Demo { 10 | private String content; 11 | 12 | public Demo(String content) { 13 | this.content = content; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-feign-test/src/main/java/com/zkdlu/feign/source/DemoClient.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.feign.source; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | 7 | @Component 8 | @FeignClient(name = "demoClient", url = "http://localhost:8080", fallback = DemoClientFallback.class) 9 | public interface DemoClient { 10 | @GetMapping("/demo1") 11 | Demo demo1(); 12 | 13 | @GetMapping("/demo2") 14 | Demo demo2(); 15 | 16 | @GetMapping("/demo3") 17 | Demo demo3(); 18 | 19 | @GetMapping("/demo4") 20 | Demo demo4(); 21 | } 22 | -------------------------------------------------------------------------------- /spring-feign-test/src/main/java/com/zkdlu/feign/source/DemoClientFallback.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.feign.source; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class DemoClientFallback implements DemoClient { 7 | @Override 8 | public Demo demo1() { 9 | return new Demo("fallback1"); 10 | } 11 | 12 | @Override 13 | public Demo demo2() { 14 | return new Demo("fallback2"); 15 | } 16 | 17 | @Override 18 | public Demo demo3() { 19 | return new Demo("fallback3"); 20 | } 21 | 22 | @Override 23 | public Demo demo4() { 24 | return new Demo("fallback4"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-feign-test/src/main/java/com/zkdlu/feign/source/EntryApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.feign.source; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RequiredArgsConstructor 10 | @RestController 11 | public class EntryApi { 12 | private final ObjectMapper objectMapper; 13 | private final DemoClient demoClient; 14 | 15 | @GetMapping("/correct") 16 | public String correct() throws JsonProcessingException { 17 | var demo = demoClient.demo1(); 18 | return objectMapper.writeValueAsString(demo); 19 | } 20 | 21 | @GetMapping("/incorrect") 22 | public String incorrect() throws JsonProcessingException { 23 | var demo = demoClient.demo2(); 24 | return objectMapper.writeValueAsString(demo); 25 | } 26 | 27 | @GetMapping("/exception") 28 | public String exception() throws JsonProcessingException { 29 | var demo = demoClient.demo3(); 30 | return objectMapper.writeValueAsString(demo); 31 | } 32 | 33 | @GetMapping("/fallback") 34 | public String circuitOpen() throws JsonProcessingException { 35 | var demo = demoClient.demo4(); 36 | 37 | return objectMapper.writeValueAsString(demo); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-feign-test/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | #feign: 2 | # client: 3 | # config: 4 | # demoClient: 5 | # connectTimeout: 10000 6 | # readTimeout: 1000 7 | # default: 8 | # connectTimeout: 10000 9 | # readTimeout: 1000 10 | 11 | feign: 12 | circuitbreaker: 13 | enabled: true 14 | client: 15 | config: 16 | default: 17 | connectTimeout: 5000 18 | readTimeout: 1000 19 | -------------------------------------------------------------------------------- /spring-kafka/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation 'org.springframework.kafka:spring-kafka' 4 | compileOnly 'org.projectlombok:lombok' 5 | annotationProcessor 'org.projectlombok:lombok' 6 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 7 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 8 | } 9 | testImplementation 'org.springframework.kafka:spring-kafka-test' 10 | } -------------------------------------------------------------------------------- /spring-kafka/src/main/java/com/zkdlu/kafka/KafkaApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.kafka; 2 | 3 | import com.zkdlu.kafka.service.Producer; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.ApplicationArguments; 6 | import org.springframework.boot.ApplicationRunner; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | 10 | @SpringBootApplication 11 | public class KafkaApplication implements ApplicationRunner { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(KafkaApplication.class, args); 15 | } 16 | 17 | @Autowired 18 | Producer producer; 19 | 20 | @Override 21 | public void run(ApplicationArguments args) throws Exception { 22 | for (int i = 0; i < 100; i++) { 23 | producer.sendMessage("test", i + ""); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-kafka/src/main/java/com/zkdlu/kafka/api/TestApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.kafka.api; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import java.util.Random; 7 | 8 | @RestController 9 | public class TestApi { 10 | private static Random random = new Random(); 11 | @GetMapping("/") 12 | public String test() { 13 | if (random.nextInt(2) < 1) { 14 | throw new RuntimeException(); 15 | } 16 | return "Hello world"; 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /spring-kafka/src/main/java/com/zkdlu/kafka/service/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.kafka.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.kafka.common.TopicPartition; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | import org.springframework.kafka.listener.ConsumerSeekAware; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Map; 11 | 12 | @Slf4j 13 | @Component 14 | public class Consumer implements ConsumerSeekAware { 15 | @Value("${kafka.topics.test}") 16 | private String topic; 17 | 18 | @KafkaListener(topics = "${kafka.topics.test}", groupId = "${spring.kafka.consumer.group-id}") 19 | void listen(String message) { 20 | log.info("1. {}", message); 21 | } 22 | 23 | @Override 24 | public void onPartitionsAssigned(Map assignments, ConsumerSeekCallback callback) { 25 | assignments.keySet().stream() 26 | .filter(partition -> topic.equals(partition.topic())) 27 | .forEach(partition -> callback.seekToBeginning(topic, partition.partition())); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-kafka/src/main/java/com/zkdlu/kafka/service/Producer.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.kafka.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.kafka.core.KafkaTemplate; 5 | import org.springframework.stereotype.Component; 6 | 7 | @RequiredArgsConstructor 8 | @Component 9 | public class Producer { 10 | private final KafkaTemplate kafkaTemplate; 11 | 12 | public void sendMessage(String topic, String message) { 13 | kafkaTemplate.send(topic, message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-kafka/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | kafka: 3 | consumer: 4 | group-id: myGroup 5 | bootstrap-servers: localhost:9092 6 | 7 | kafka: 8 | topics: 9 | test: my-topic -------------------------------------------------------------------------------- /spring-kafka/src/test/java/com/zkdlu/kafka/KafkaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.kafka; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class KafkaApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | compileOnly 'org.projectlombok:lombok' 4 | annotationProcessor 'org.projectlombok:lombok' 5 | } -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/OrderApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | @SpringBootApplication 9 | public class OrderApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(OrderApplication.class ,args); 12 | } 13 | 14 | @Bean 15 | public RestTemplate restTemplate() { 16 | return new RestTemplate(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/api/Cart.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.api; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Getter 10 | @NoArgsConstructor 11 | public class Cart { 12 | private List items = new ArrayList<>(); 13 | } 14 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/api/CartItem.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.api; 2 | 3 | import com.zkdlu.order.domain.OrderLineItem; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor 9 | public class CartItem { 10 | private String id; 11 | private String name; 12 | private int price; 13 | 14 | public OrderLineItem toOrderLineItem() { 15 | return new OrderLineItem(id, name, price); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/api/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.api; 2 | 3 | import com.zkdlu.order.service.OrderService; 4 | import com.zkdlu.order.service.PayRequest; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.annotation.CrossOrigin; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.stream.Collectors; 13 | 14 | @Slf4j 15 | @CrossOrigin("*") 16 | @RequiredArgsConstructor 17 | @RestController 18 | public class OrderController { 19 | private final OrderService orderService; 20 | 21 | @PostMapping("/order") 22 | public PayRequest order(@RequestBody Cart cart) { 23 | return orderService.placeOrder(cart.getItems() 24 | .stream() 25 | .map(CartItem::toOrderLineItem) 26 | .collect(Collectors.toList()) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/api/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.api; 2 | 3 | public class OrderMapper { 4 | } 5 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.domain; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @NoArgsConstructor 11 | public class Order { 12 | private String id; 13 | private List orderItems; 14 | 15 | @Builder 16 | public Order(String id, List orderItems) { 17 | this.id = id; 18 | this.orderItems = orderItems; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/domain/OrderLineItem.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrderLineItem { 11 | private String id; 12 | private String name; 13 | private int price; 14 | } 15 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.service; 2 | 3 | import com.zkdlu.order.domain.Order; 4 | import com.zkdlu.order.domain.OrderLineItem; 5 | import com.zkdlu.order.service.remote.PayReady; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | @RequiredArgsConstructor 14 | @Service 15 | public class OrderService { 16 | private final RestTemplate restTemplate; 17 | 18 | public PayRequest placeOrder(List orderLineItems) { 19 | Order order = Order.builder() 20 | .id(UUID.randomUUID().toString()) 21 | .orderItems(orderLineItems) 22 | .build(); 23 | 24 | var payReady = restTemplate.postForObject("http://localhost:8082/pay", order, PayReady.class); 25 | 26 | return PayRequest.builder() 27 | .order(order) 28 | .payReady(payReady) 29 | .build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/service/PayRequest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.service; 2 | 3 | import com.zkdlu.order.domain.Order; 4 | import com.zkdlu.order.service.remote.PayReady; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | public class PayRequest { 12 | private Order order; 13 | private PayReady payReady; 14 | 15 | @Builder 16 | public PayRequest(Order order, PayReady payReady) { 17 | this.order = order; 18 | this.payReady = payReady; 19 | } 20 | } -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/java/com/zkdlu/order/service/remote/PayReady.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.order.service.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | public class PayReady { 12 | @JsonProperty("tid") 13 | private String tid; 14 | @JsonProperty("next_redirect_pc_url") 15 | private String nextRedirectPcUrl; 16 | @JsonProperty("created_at") 17 | private Date createdAt; 18 | } 19 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-order/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8083 -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | compileOnly 'org.projectlombok:lombok' 4 | annotationProcessor 'org.projectlombok:lombok' 5 | } -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/PaymentApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | @SpringBootApplication 9 | public class PaymentApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(PaymentApplication.class, args); 12 | } 13 | 14 | @Bean 15 | public RestTemplate restTemplate() { 16 | return new RestTemplate(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/api/PayController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.api; 2 | 3 | import com.zkdlu.payment.service.PayService; 4 | import com.zkdlu.payment.service.remote.Order; 5 | import com.zkdlu.payment.service.remote.PayReady; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.CrossOrigin; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | import org.springframework.web.bind.annotation.ResponseBody; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import java.io.IOException; 18 | 19 | @Slf4j 20 | @CrossOrigin("*") 21 | @RequiredArgsConstructor 22 | @Controller 23 | public class PayController { 24 | private final PayService payService; 25 | 26 | @PostMapping("/pay") 27 | @ResponseBody 28 | public PayReady kakaoPay(@RequestBody Order order, HttpServletRequest request) throws IOException { 29 | var payReady = payService.prepare(order); 30 | log.info(payReady.getTid()); 31 | 32 | var session = request.getSession(true); 33 | session.setAttribute(order.getId(), payReady); 34 | 35 | return payReady; 36 | } 37 | 38 | @GetMapping("/pay/success") 39 | @ResponseBody 40 | public String kakaoPaySuccess(@RequestParam("pg_token") String pg_token, 41 | @RequestParam String order) { 42 | 43 | log.info(pg_token + " : " + order); 44 | 45 | return pg_token + " : " + order; 46 | } 47 | 48 | @GetMapping("/pay/fail") 49 | @ResponseBody 50 | public String kakaoPayFail() { 51 | return "faile"; 52 | } 53 | 54 | @GetMapping("/pay/cancel") 55 | @ResponseBody 56 | public String kakaoPayCancel() { 57 | return "cancel"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/domain/Payment.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.domain; 2 | 3 | import com.zkdlu.payment.service.remote.Order; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | public class Payment { 9 | public enum State { 10 | PREPARE, PAYED, COMPLETE 11 | } 12 | 13 | private String id; 14 | private Order order; 15 | private State state; 16 | 17 | @Builder 18 | public Payment(String id, Order order) { 19 | this.id = id; 20 | this.order = order; 21 | this.state = State.PREPARE; 22 | } 23 | 24 | public void pay() { 25 | this.state = State.PAYED; 26 | } 27 | 28 | public void complete() { 29 | this.state = State.COMPLETE; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/domain/PaymentRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.domain; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | public interface PaymentRepository { 7 | List findAll(); 8 | Optional findById(String paymentId); 9 | Payment save(Payment payment); 10 | } 11 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/domain/PaymentRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.domain; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedHashSet; 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | 11 | @Repository 12 | public class PaymentRepositoryImpl implements PaymentRepository{ 13 | private Set payments = new LinkedHashSet<>(); 14 | 15 | @Override 16 | public List findAll() { 17 | return new ArrayList<>(payments); 18 | } 19 | 20 | @Override 21 | public Optional findById(String paymentId) { 22 | return payments.stream() 23 | .filter(p -> p.getId().equals(paymentId)) 24 | .findFirst(); 25 | } 26 | 27 | @Override 28 | public Payment save(Payment payment) { 29 | payments.add(payment); 30 | 31 | return payment; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/service/PayService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.service; 2 | 3 | import com.zkdlu.payment.service.remote.Order; 4 | import com.zkdlu.payment.service.remote.PayReady; 5 | 6 | public interface PayService { 7 | PayReady prepare(Order order); 8 | void pay(String paymentId); 9 | void complete(String paymentId); 10 | } 11 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/service/remote/Order.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.service.remote; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @NoArgsConstructor 11 | public class Order { 12 | private String id; 13 | private List orderItems; 14 | 15 | @Builder 16 | public Order(String id, List orderItems) { 17 | this.id = id; 18 | this.orderItems = orderItems; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/service/remote/OrderLineItem.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.service.remote; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrderLineItem { 11 | private String id; 12 | private String name; 13 | private int price; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/service/remote/PayReady.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.service.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | public class PayReady { 12 | @JsonProperty("tid") 13 | private String tid; 14 | @JsonProperty("next_redirect_pc_url") 15 | private String nextRedirectPcUrl; 16 | @JsonProperty("created_at") 17 | private Date createdAt; 18 | } 19 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/java/com/zkdlu/payment/service/remote/Product.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.service.remote; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.util.UUID; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | public class Product { 11 | private UUID id; 12 | private String name; 13 | private String image; 14 | private int price; 15 | private int stock; 16 | } 17 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 3 | 4 | spring: 5 | devtools: 6 | livereload: 7 | enabled: true 8 | restart: 9 | enabled: false 10 | thymeleaf: 11 | cache: false -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/test/java/com/zkdlu/payment/domain/PaymentTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.domain; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class PaymentTest { 9 | @Test 10 | void paymentTest() { 11 | Payment payment = Payment.builder() 12 | .id("pay-1") 13 | .build(); 14 | 15 | assertThat(payment.getState()).isEqualTo(Payment.State.PREPARE); 16 | } 17 | 18 | @Test 19 | void payedTest() { 20 | Payment payment = Payment.builder() 21 | .id("pay-1") 22 | .build(); 23 | 24 | payment.pay(); 25 | 26 | assertThat(payment.getState()).isEqualTo(Payment.State.PAYED); 27 | } 28 | 29 | @Test 30 | void completeTest() { 31 | Payment payment = Payment.builder() 32 | .id("pay-1") 33 | .build(); 34 | 35 | payment.complete(); 36 | 37 | assertThat(payment.getState()).isEqualTo(Payment.State.COMPLETE); 38 | } 39 | } -------------------------------------------------------------------------------- /spring-msa-transaction/service-payment/src/test/java/com/zkdlu/payment/service/KakaoPayTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.payment.service; 2 | 3 | import com.zkdlu.payment.domain.PaymentRepository; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | @ExtendWith(MockitoExtension.class) 13 | class KakaoPayTest { 14 | @Mock 15 | PaymentRepository paymentRepository; 16 | @InjectMocks 17 | KakaoPay kakaoPay; 18 | 19 | @Test 20 | void prepare() { 21 | //given 22 | //when 23 | //then 24 | } 25 | 26 | @Test 27 | void pay() { 28 | //given 29 | //when 30 | // then 31 | } 32 | } -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | compileOnly 'org.projectlombok:lombok' 4 | annotationProcessor 'org.projectlombok:lombok' 5 | } -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/java/com/zkdlu/product/ProductApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(ProductApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/java/com/zkdlu/product/api/ProductApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product.api; 2 | 3 | import com.zkdlu.product.domain.Product; 4 | import com.zkdlu.product.service.ProductService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.CrossOrigin; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | @CrossOrigin("*") 15 | @RequiredArgsConstructor 16 | @RestController 17 | public class ProductApi { 18 | private final ProductService productService; 19 | 20 | @GetMapping("/products") 21 | public List getProducts() { 22 | return productService.getProducts(); 23 | } 24 | 25 | @GetMapping("/products/page/{page}") 26 | public List getProductPage(@PathVariable int page) { 27 | return productService.getProductsByPage(page); 28 | } 29 | 30 | @GetMapping("/products/detail/{uuid}") 31 | public Product getProduct(@PathVariable UUID uuid) { 32 | return productService.getProductDetail(uuid); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/java/com/zkdlu/product/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product.domain; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.UUID; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | public class Product { 12 | private UUID id; 13 | private String name; 14 | private String image; 15 | private int price; 16 | private int stock; 17 | 18 | @Builder 19 | public Product(UUID id, String name, String image, int price, int stock) { 20 | this.id = id; 21 | this.name = name; 22 | this.image = image; 23 | this.price = price; 24 | this.stock = stock; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/java/com/zkdlu/product/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product.domain; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import java.util.UUID; 6 | 7 | public interface ProductRepository { 8 | List findAll(int page); 9 | List findAll(); 10 | Optional findById(UUID uuid); 11 | Product save(Product product); 12 | } 13 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/java/com/zkdlu/product/domain/ProductRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product.domain; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import javax.annotation.PostConstruct; 6 | import java.util.ArrayList; 7 | import java.util.LinkedHashSet; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.Set; 11 | import java.util.UUID; 12 | import java.util.stream.Collectors; 13 | 14 | @Repository 15 | public class ProductRepositoryImpl implements ProductRepository { 16 | private Set products = new LinkedHashSet<>(); 17 | 18 | @PostConstruct 19 | public void init() { 20 | for (int i = 0; i < 100; i++) { 21 | products.add(Product.builder() 22 | .id(UUID.randomUUID()) 23 | .name("옷" + i) 24 | .image("https://placeimg.com/100/100/any") 25 | .price(1000 + i * 1000) 26 | .stock(i) 27 | .build()); 28 | products.add(Product.builder() 29 | .id(UUID.randomUUID()) 30 | .name("신발" + i) 31 | .price(1000 + i * 1000) 32 | .stock(i) 33 | .image("https://placeimg.com/100/100/any") 34 | .build()); 35 | products.add(Product.builder() 36 | .id(UUID.randomUUID()) 37 | .name("장난감" + i) 38 | .image("https://placeimg.com/100/100/any") 39 | .price(1000 + i * 1000) 40 | .stock(i) 41 | .build()); 42 | } 43 | } 44 | 45 | @Override 46 | public List findAll(int page) { 47 | return products.stream() 48 | .skip(page * 42) 49 | .limit(42) 50 | .collect(Collectors.toList()); 51 | } 52 | 53 | @Override 54 | public List findAll() { 55 | return new ArrayList<>(products); 56 | } 57 | 58 | @Override 59 | public Optional findById(UUID uuid) { 60 | return products.stream() 61 | .filter(p -> p.getId().equals(uuid)) 62 | .findFirst(); 63 | } 64 | 65 | @Override 66 | public Product save(Product product) { 67 | products.add(product); 68 | 69 | return product; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/java/com/zkdlu/product/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product.service; 2 | 3 | import com.zkdlu.product.domain.Product; 4 | import com.zkdlu.product.domain.ProductRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | @RequiredArgsConstructor 12 | @Service 13 | public class ProductService { 14 | private final ProductRepository productRepository; 15 | 16 | public List getProducts() { 17 | return productRepository.findAll(); 18 | } 19 | 20 | public List getProductsByPage(int page) { 21 | return productRepository.findAll(page); 22 | } 23 | 24 | public Product getProductDetail(UUID uuid) { 25 | return productRepository.findById(uuid) 26 | .orElseThrow(IllegalAccessError::new); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 -------------------------------------------------------------------------------- /spring-msa-transaction/service-product/src/test/java/com/zkdlu/product/service/ProductServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product.service; 2 | 3 | import com.zkdlu.product.domain.Product; 4 | import com.zkdlu.product.domain.ProductRepository; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.UUID; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.junit.jupiter.api.Assertions.*; 20 | import static org.mockito.BDDMockito.given; 21 | 22 | @ExtendWith(MockitoExtension.class) 23 | class ProductServiceTest { 24 | @Mock 25 | ProductRepository productRepository; 26 | @InjectMocks 27 | ProductService productService; 28 | 29 | List productList; 30 | 31 | @BeforeEach 32 | void setUp() { 33 | productList = Arrays.asList( 34 | Product.builder().id(UUID.fromString("edfe8ef2-68ab-4c8b-afaf-286e6bfbdabc")) 35 | .name("1") 36 | .image("https://placeimg.com/100/100/any") 37 | .stock(1) 38 | .build() 39 | ); 40 | } 41 | 42 | @Test 43 | @DisplayName("전체 제품 목록 조회") 44 | void getProducts() { 45 | //given 46 | given(productRepository.findAll()).willReturn(productList); 47 | 48 | //when 49 | var result = productService.getProducts(); 50 | 51 | //then 52 | assertThat(result).isEqualTo(productList); 53 | } 54 | 55 | @Test 56 | @DisplayName("페이지별 제품 목록 조회") 57 | void getProductsByPage() { 58 | //given 59 | given(productRepository.findAll(0)).willReturn(productList); 60 | 61 | //when 62 | var result = productService.getProductsByPage(0); 63 | 64 | //then 65 | assertThat(result).isEqualTo(productList); 66 | } 67 | 68 | @Test 69 | @DisplayName("제품 상세 조회") 70 | void getProductDetail() { 71 | //given 72 | UUID uuid = UUID.fromString("edfe8ef2-68ab-4c8b-afaf-286e6bfbdabc"); 73 | given(productRepository.findById(uuid)).willReturn(Optional.of(Product.builder() 74 | .id(uuid) 75 | .image("image") 76 | .name("name") 77 | .stock(0) 78 | .build())); 79 | 80 | //when 81 | var result = productService.getProductDetail(uuid); 82 | 83 | //then 84 | assertThat(result.getId()).isEqualTo(uuid); 85 | } 86 | } -------------------------------------------------------------------------------- /spring-msa-transaction/web/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-devtools' 3 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 4 | implementation 'org.springframework.boot:spring-boot-starter-web' 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | } 8 | -------------------------------------------------------------------------------- /spring-msa-transaction/web/src/main/java/com/zkdlu/WebApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class WebApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(WebApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-msa-transaction/web/src/main/java/com/zkdlu/web/WebController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.web; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.CrossOrigin; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | 9 | @CrossOrigin("*") 10 | @Controller 11 | public class WebController { 12 | @GetMapping 13 | public String index() { 14 | return "index.html"; 15 | } 16 | 17 | @GetMapping("/order/{productId}") 18 | public String order(Model model, @PathVariable String productId) { 19 | model.addAttribute("productId", productId); 20 | 21 | return "order.html"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-msa-transaction/web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | devtools: 3 | livereload: 4 | enabled: true 5 | restart: 6 | enabled: false 7 | thymeleaf: 8 | cache: false -------------------------------------------------------------------------------- /spring-msa-transaction/web/src/main/resources/static/images/payment_icon_yellow_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkdlu/spring-boot/6790c634fcd147659a856b4bf5b764af387a700a/spring-msa-transaction/web/src/main/resources/static/images/payment_icon_yellow_large.png -------------------------------------------------------------------------------- /spring-oauth-jwt/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'javax.xml.bind:jaxb-api' 3 | implementation 'io.jsonwebtoken:jjwt:0.9.1' 4 | implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' 5 | implementation 'org.springframework.boot:spring-boot-starter-security' 6 | implementation 'org.springframework.boot:spring-boot-starter-web' 7 | compileOnly 'org.projectlombok:lombok' 8 | annotationProcessor 'org.projectlombok:lombok' 9 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 10 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 11 | } 12 | testImplementation 'org.springframework.security:spring-security-test' 13 | } -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/OauthappApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 6 | 7 | @EnableWebSecurity 8 | @SpringBootApplication 9 | public class OauthappApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(OauthappApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/api/TestApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.api; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class TestApi { 8 | @GetMapping("/test") 9 | public String index() { 10 | return "Hello World"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/api/TokenController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.api; 2 | 3 | import com.zkdlu.oauthapp.service.Token; 4 | import com.zkdlu.oauthapp.service.TokenService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | @RequiredArgsConstructor 13 | @RestController 14 | public class TokenController { 15 | private final TokenService tokenService; 16 | 17 | @GetMapping("/token/expired") 18 | public String auth() { 19 | throw new RuntimeException(); 20 | } 21 | 22 | @GetMapping("/token/refresh") 23 | public String refreshAuth(HttpServletRequest request, HttpServletResponse response) { 24 | String token = request.getHeader("Refresh"); 25 | 26 | if (token != null && tokenService.verifyToken(token)) { 27 | String email = tokenService.getUid(token); 28 | Token newToken = tokenService.generateToken(email, "USER"); 29 | 30 | response.addHeader("Auth", newToken.getToken()); 31 | response.addHeader("Refresh", newToken.getRefreshToken()); 32 | response.setContentType("application/json;charset=UTF-8"); 33 | 34 | return "HAPPY NEW TOKEN"; 35 | } 36 | 37 | throw new RuntimeException(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/config/JwtAuthFilter.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.config; 2 | 3 | import com.zkdlu.oauthapp.service.TokenService; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.web.filter.GenericFilterBean; 10 | 11 | import javax.servlet.FilterChain; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.io.IOException; 17 | import java.util.Arrays; 18 | 19 | @RequiredArgsConstructor 20 | public class JwtAuthFilter extends GenericFilterBean { 21 | private final TokenService tokenService; 22 | 23 | @Override 24 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 25 | String token = ((HttpServletRequest)request).getHeader("Auth"); 26 | 27 | if (token != null && tokenService.verifyToken(token)) { 28 | String email = tokenService.getUid(token); 29 | 30 | UserDto userDto = UserDto.builder() 31 | .email(email) 32 | .name("이름이에용") 33 | .picture("프로필 이미지에요").build(); 34 | 35 | Authentication auth = getAuthentication(userDto); 36 | SecurityContextHolder.getContext().setAuthentication(auth); 37 | } 38 | 39 | chain.doFilter(request, response); 40 | } 41 | 42 | public Authentication getAuthentication(UserDto member) { 43 | return new UsernamePasswordAuthenticationToken(member, "", 44 | Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); 45 | } 46 | } -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/config/OAuth2SuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.zkdlu.oauthapp.service.Token; 5 | import com.zkdlu.oauthapp.service.TokenService; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.oauth2.core.user.OAuth2User; 10 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 11 | import org.springframework.stereotype.Component; 12 | 13 | import javax.servlet.ServletException; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | @Slf4j 19 | @RequiredArgsConstructor 20 | @Component 21 | public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { 22 | private final TokenService tokenService; 23 | private final UserRequestMapper userRequestMapper; 24 | private final ObjectMapper objectMapper; 25 | 26 | @Override 27 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 28 | OAuth2User oAuth2User = (OAuth2User)authentication.getPrincipal(); 29 | UserDto userDto = userRequestMapper.toDto(oAuth2User); 30 | 31 | Token token = tokenService.generateToken(userDto.getEmail(), "USER"); 32 | log.info("{}", token); 33 | 34 | writeTokenResponse(response, token); 35 | } 36 | 37 | private void writeTokenResponse(HttpServletResponse response, Token token) throws IOException { 38 | response.setContentType("text/html;charset=UTF-8"); 39 | 40 | response.addHeader("Auth", token.getToken()); 41 | response.addHeader("Refresh", token.getRefreshToken()); 42 | response.setContentType("application/json;charset=UTF-8"); 43 | 44 | var writer = response.getWriter(); 45 | writer.println(objectMapper.writeValueAsString(token)); 46 | writer.flush(); 47 | } 48 | } -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.config; 2 | 3 | import com.zkdlu.oauthapp.service.CustomOAuth2UserService; 4 | import com.zkdlu.oauthapp.service.TokenService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.config.http.SessionCreationPolicy; 10 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 11 | 12 | @RequiredArgsConstructor 13 | @Configuration 14 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 15 | private final CustomOAuth2UserService oAuth2UserService; 16 | private final OAuth2SuccessHandler successHandler; 17 | private final TokenService tokenService; 18 | 19 | @Override 20 | protected void configure(HttpSecurity http) throws Exception { 21 | http.httpBasic().disable() 22 | .csrf().disable() 23 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 24 | .and() 25 | .authorizeRequests() 26 | .antMatchers("/token/**").permitAll() 27 | .anyRequest().authenticated() 28 | .and() 29 | .oauth2Login().loginPage("/token/expired") 30 | .successHandler(successHandler) 31 | .userInfoEndpoint().userService(oAuth2UserService); 32 | 33 | http.addFilterBefore(new JwtAuthFilter(tokenService), UsernamePasswordAuthenticationFilter.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/config/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.config; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @Getter 9 | public class UserDto { 10 | private String email; 11 | private String name; 12 | private String picture; 13 | 14 | @Builder 15 | public UserDto(String email, String name, String picture) { 16 | this.email = email; 17 | this.name = name; 18 | this.picture = picture; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/config/UserRequestMapper.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.config; 2 | 3 | import org.springframework.security.oauth2.core.user.OAuth2User; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class UserRequestMapper { 8 | public UserDto toDto(OAuth2User oAuth2User) { 9 | var attributes = oAuth2User.getAttributes(); 10 | return UserDto.builder() 11 | .email((String)attributes.get("email")) 12 | .name((String)attributes.get("name")) 13 | .picture((String)attributes.get("picture")) 14 | .build(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/service/CustomOAuth2UserService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 | import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; 6 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; 7 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; 8 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 9 | import org.springframework.security.oauth2.core.user.DefaultOAuth2User; 10 | import org.springframework.security.oauth2.core.user.OAuth2User; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Collections; 14 | 15 | @Slf4j 16 | @Service 17 | public class CustomOAuth2UserService implements OAuth2UserService { 18 | @Override 19 | public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { 20 | OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService(); 21 | OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest); 22 | 23 | String registrationId = userRequest.getClientRegistration().getRegistrationId(); 24 | String userNameAttributeName = userRequest.getClientRegistration() 25 | .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); 26 | 27 | OAuth2Attribute oAuth2Attribute = 28 | OAuth2Attribute.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); 29 | 30 | log.info("{}", oAuth2Attribute); 31 | 32 | var memberAttribute = oAuth2Attribute.convertToMap(); 33 | 34 | return new DefaultOAuth2User( 35 | Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")), 36 | memberAttribute, "email"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/service/Token.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.service; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.ToString; 6 | 7 | @ToString 8 | @NoArgsConstructor 9 | @Getter 10 | public class Token { 11 | private String token; 12 | private String refreshToken; 13 | 14 | public Token(String token, String refreshToken) { 15 | this.token = token; 16 | this.refreshToken = refreshToken; 17 | } 18 | } -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/java/com/zkdlu/oauthapp/service/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp.service; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jws; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.SignatureAlgorithm; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.Base64; 11 | import java.util.Date; 12 | 13 | @Service 14 | public class TokenService { 15 | private String secretKey = "token-secret-key"; 16 | 17 | @PostConstruct 18 | protected void init() { 19 | secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); 20 | } 21 | 22 | public Token generateToken(String uid, String role) { 23 | long tokenPeriod = 1000L * 60L * 10L; 24 | long refreshPeriod = 1000L * 60L * 60L * 24L * 30L * 3L; 25 | 26 | Claims claims = Jwts.claims().setSubject(uid); 27 | claims.put("role", role); 28 | 29 | Date now = new Date(); 30 | return new Token( 31 | Jwts.builder() 32 | .setClaims(claims) 33 | .setIssuedAt(now) 34 | .setExpiration(new Date(now.getTime() + tokenPeriod)) 35 | .signWith(SignatureAlgorithm.HS256, secretKey) 36 | .compact(), 37 | Jwts.builder() 38 | .setClaims(claims) 39 | .setIssuedAt(now) 40 | .setExpiration(new Date(now.getTime() + refreshPeriod)) 41 | .signWith(SignatureAlgorithm.HS256, secretKey) 42 | .compact()); 43 | } 44 | 45 | public boolean verifyToken(String token) { 46 | try { 47 | Jws claims = Jwts.parser() 48 | .setSigningKey(secretKey) 49 | .parseClaimsJws(token); 50 | return claims.getBody() 51 | .getExpiration() 52 | .after(new Date()); 53 | } catch (Exception e) { 54 | return false; 55 | } 56 | } 57 | 58 | public String getUid(String token) { 59 | return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); 60 | } 61 | } -------------------------------------------------------------------------------- /spring-oauth-jwt/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | security: 3 | oauth2: 4 | client: 5 | registration: 6 | kakao: 7 | client-id: 8 | client-secret: > 9 | redirect-uri: /login/oauth2/code/kakao 10 | authorization-grant-type: authorization_code 11 | client-authentication-method: POST 12 | client-name: Kakao 13 | scope: 14 | - profile 15 | - account_email 16 | naver: 17 | client-id: 18 | client-secret: 19 | redirect-uri: /login/oauth2/code/naver 20 | authorization-grant-type: authorization_code 21 | scope: 22 | - name 23 | - email 24 | google: 25 | client-id: 26 | client-secret: 27 | scope: 28 | - profile 29 | - email 30 | provider: 31 | kakao: 32 | authorization-uri: https://kauth.kakao.com/oauth/authorize 33 | token-uri: https://kauth.kakao.com/oauth/token 34 | user-info-uri: https://kapi.kakao.com/v2/user/me 35 | user-name-attribute: id 36 | naver: 37 | authorization-uri: https://nid.naver.com/oauth2.0/authorize 38 | token-uri: https://nid.naver.com/oauth2.0/token 39 | user-info-uri: https://openapi.naver.com/v1/nid/me 40 | user-name-attribute: response 41 | -------------------------------------------------------------------------------- /spring-oauth-jwt/src/test/java/com/zkdlu/oauthapp/OauthappApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.oauthapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class OauthappApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-object-binding/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 3 | implementation 'org.springframework.boot:spring-boot-starter-web' 4 | runtimeOnly 'com.h2database:h2' 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 8 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 9 | } 10 | 11 | 12 | implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' 13 | } -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/BindingApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | 7 | @EnableFeignClients 8 | @SpringBootApplication 9 | public class BindingApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(BindingApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/BodyData.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import lombok.Getter; 4 | 5 | public class BodyData { 6 | @Getter 7 | public static class BodyDataRequest { 8 | private String id; 9 | private String name; 10 | 11 | public BodyDataResponse toResponse() { 12 | return new BodyDataResponse(id, name); 13 | } 14 | } 15 | 16 | @Getter 17 | public static class BodyDataResponse { 18 | private String id; 19 | private String name; 20 | 21 | public BodyDataResponse(String id, String name) { 22 | this.id = id; 23 | this.name = name; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/DemoApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import com.zkdlu.binding.BodyData.BodyDataRequest; 4 | import com.zkdlu.binding.BodyData.BodyDataResponse; 5 | import com.zkdlu.binding.ModelAttributeData.ModelAttributeRequest; 6 | import com.zkdlu.binding.feign.FeignService; 7 | import org.springframework.util.MultiValueMap; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.ModelAttribute; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | public class DemoApi { 16 | private final DemoService demoService; 17 | private final FeignService feignService; 18 | 19 | public DemoApi(DemoService demoService, FeignService feignService) { 20 | this.demoService = demoService; 21 | this.feignService = feignService; 22 | } 23 | 24 | @PostMapping("/requestBody") 25 | public BodyDataResponse postWithRequestBody(@RequestBody BodyDataRequest body) { 26 | return demoService.mapFrom(body); 27 | } 28 | 29 | @PostMapping("/modelattribute") 30 | public ModelAttributeData.ModelAttributeResponse postWithModelAttribute(@ModelAttribute ModelAttributeRequest model) { 31 | return demoService.mapFrom(model); 32 | } 33 | 34 | @PostMapping("/modelattribute2") 35 | public ModelAttributeData.ModelAttributeResponse postWithModelAttribute2(@ModelAttribute MultiValueMap model) { 36 | return new ModelAttributeData.ModelAttributeResponse("1", "2", null); 37 | } 38 | 39 | 40 | @GetMapping("/target") 41 | public String target() { 42 | return "target"; 43 | } 44 | 45 | @GetMapping("/mytest") 46 | public ModelAttributeData.ModelAttributeResponse myTest() { 47 | return feignService.foo(); 48 | } 49 | 50 | @GetMapping("/mytest2") 51 | public ModelAttributeData.ModelAttributeResponse myTest2() { 52 | return feignService.hoo(); 53 | } 54 | 55 | @GetMapping("/mytest3") 56 | public ModelAttributeData.ModelAttributeResponse myTest3() { 57 | return feignService.goo(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/DemoService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | public interface DemoService { 4 | BodyData.BodyDataResponse mapFrom(BodyData.BodyDataRequest request); 5 | 6 | ModelAttributeData.ModelAttributeResponse mapFrom(ModelAttributeData.ModelAttributeRequest model); 7 | } 8 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/DemoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class DemoServiceImpl implements DemoService { 7 | @Override 8 | public BodyData.BodyDataResponse mapFrom(BodyData.BodyDataRequest request) { 9 | return null; 10 | } 11 | 12 | @Override 13 | public ModelAttributeData.ModelAttributeResponse mapFrom(ModelAttributeData.ModelAttributeRequest model) { 14 | ModelAttributeData.ModelAttributeResponse response = new ModelAttributeData.ModelAttributeResponse(); 15 | response.setId(model.getId()); 16 | response.setName(model.getName()); 17 | response.setList(model.getList()); 18 | return response; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/ModelAttributeData.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | public class ModelAttributeData { 11 | @Getter 12 | @Setter 13 | public static class ModelAttributeRequest { 14 | private String id; 15 | private String name; 16 | private List list; 17 | } 18 | 19 | @Setter 20 | @Getter 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | public static class ModelAttributeItem { 24 | private String id; 25 | private String name; 26 | 27 | @Override 28 | public String toString() { 29 | return id; 30 | } 31 | } 32 | 33 | @Setter 34 | @Getter 35 | @NoArgsConstructor 36 | @AllArgsConstructor 37 | public static class ModelAttributeResponse { 38 | private String id; 39 | private String name; 40 | private List list; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/MyFilter.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.web.filter.OncePerRequestFilter; 5 | import org.springframework.web.util.ContentCachingRequestWrapper; 6 | import org.springframework.web.util.ContentCachingResponseWrapper; 7 | 8 | import javax.servlet.FilterChain; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | @Component 15 | public class MyFilter extends OncePerRequestFilter { 16 | @Override 17 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 18 | ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); 19 | ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); 20 | 21 | logRequest(requestWrapper); 22 | 23 | filterChain.doFilter(requestWrapper, responseWrapper); 24 | 25 | 26 | responseWrapper.copyBodyToResponse(); 27 | } 28 | 29 | private void logRequest(ContentCachingRequestWrapper request) { 30 | 31 | System.out.println("===================================================="); 32 | System.out.println(new MyLog(request.getRequestURI(), request.getQueryString(), 33 | new String(request.getContentAsByteArray()))); 34 | } 35 | 36 | public static class MyLog { 37 | private String url; 38 | private String queryString; 39 | private String body; 40 | 41 | public MyLog(final String url, final String queryString, final String body) { 42 | this.url = url; 43 | this.queryString = queryString; 44 | this.body = body; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return url + ", " + queryString + ", " + body; 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/feign/FeignConfig.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding.feign; 2 | 3 | import feign.form.FormEncoder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.converter.FormHttpMessageConverter; 7 | 8 | @Configuration 9 | public class FeignConfig { 10 | @Bean 11 | public FormEncoder formEncoder() { 12 | return new FormEncoder(); 13 | } 14 | 15 | @Bean 16 | public FormHttpMessageConverter formHttpMessageConverter() { 17 | return new FormHttpMessageConverter(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/feign/FeignService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding.feign; 2 | 3 | import com.zkdlu.binding.ModelAttributeData; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.util.LinkedMultiValueMap; 7 | import org.springframework.util.MultiValueMap; 8 | 9 | import java.util.List; 10 | 11 | @RequiredArgsConstructor 12 | @Service 13 | public class FeignService { 14 | private final MyClient myClient; 15 | 16 | public ModelAttributeData.ModelAttributeResponse foo() { 17 | ModelAttributeData.ModelAttributeRequest request = new ModelAttributeData.ModelAttributeRequest(); 18 | 19 | request.setId("id1"); 20 | request.setName("name1"); 21 | request.setList(List.of( 22 | new ModelAttributeData.ModelAttributeItem("list1", "list1"), 23 | new ModelAttributeData.ModelAttributeItem("list2", "list2") 24 | )); 25 | 26 | return myClient.foo(request); 27 | } 28 | 29 | public ModelAttributeData.ModelAttributeResponse hoo() { 30 | MultiValueMap data = new LinkedMultiValueMap<>(); 31 | data.add("id", "id1"); 32 | data.add("name", "name1"); 33 | data.add("list[0].id", "list-id1"); 34 | data.add("list[0].name", "list-name1"); 35 | data.add("list[1].id", "list-id1"); 36 | data.add("list[1].name", "list-name1"); 37 | 38 | return myClient.hoo(data); 39 | } 40 | 41 | public ModelAttributeData.ModelAttributeResponse goo() { 42 | String str = "id=id1&name=name1&list[0].id=list-id1&list[0].name=list-name1"; 43 | return myClient.goo(str); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/java/com/zkdlu/binding/feign/MyClient.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding.feign; 2 | 3 | import com.zkdlu.binding.ModelAttributeData; 4 | import org.springframework.cloud.openfeign.FeignClient; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.MultiValueMap; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @Component 14 | @FeignClient(name = "myClient", url = "http://localhost:8080", configuration = FeignConfig.class) 15 | public interface MyClient { 16 | @GetMapping("/target") 17 | String target(); 18 | 19 | 20 | @PostMapping(value = "/modelattribute") 21 | ModelAttributeData.ModelAttributeResponse foo(@RequestBody ModelAttributeData.ModelAttributeRequest model); 22 | 23 | @PostMapping(value = "/modelattribute") 24 | ModelAttributeData.ModelAttributeResponse hoo(@RequestParam MultiValueMap model); 25 | 26 | @PostMapping(value = "/modelattribute", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) 27 | ModelAttributeData.ModelAttributeResponse goo(@RequestBody String str); 28 | } 29 | -------------------------------------------------------------------------------- /spring-object-binding/src/main/resources/application.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkdlu/spring-boot/6790c634fcd147659a856b4bf5b764af387a700a/spring-object-binding/src/main/resources/application.yml -------------------------------------------------------------------------------- /spring-object-binding/src/test/java/com/zkdlu/binding/DemoApiTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Nested; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.test.web.servlet.MockMvc; 8 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.hamcrest.Matchers.equalTo; 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 14 | 15 | class DemoApiTest { 16 | 17 | private MockMvc mockMvc; 18 | private SpyDemoService spyDemoService; 19 | 20 | @BeforeEach 21 | void setUp() { 22 | spyDemoService = new SpyDemoService(); 23 | mockMvc = MockMvcBuilders.standaloneSetup(new DemoApi(spyDemoService, null)).build(); 24 | } 25 | 26 | @Nested 27 | public class PostRequestBody { 28 | @Test 29 | void postWithRequestBody_passesRequestToService() throws Exception { 30 | String json = "{\"id\":\"id1\",\"name\":\"name1\"}"; 31 | mockMvc.perform(post("/requestBody") 32 | .contentType(MediaType.APPLICATION_JSON) 33 | .content(json)); 34 | 35 | assertThat(spyDemoService.mapFrom_argumentRequestBody.getId()).isEqualTo("id1"); 36 | assertThat(spyDemoService.mapFrom_argumentRequestBody.getName()).isEqualTo("name1"); 37 | } 38 | 39 | @Test 40 | void postWithRequestBody_returnsRequestData() throws Exception { 41 | String json = "{\"id\":\"id1\",\"name\":\"name1\"}"; 42 | mockMvc.perform(post("/requestBody") 43 | .contentType(MediaType.APPLICATION_JSON) 44 | .content(json)) 45 | .andExpect(jsonPath("$.id", equalTo("id1"))) 46 | .andExpect(jsonPath("$.name", equalTo("name1"))); 47 | } 48 | } 49 | 50 | @Nested 51 | public class PostModelAttribute { 52 | @Test 53 | void postWithModelAttribute_passesRequestToService() throws Exception { 54 | mockMvc.perform(post("/modelattribute") 55 | .param("id", "id1") 56 | .param("name", "name1") 57 | .param("list[0].id", "list-id") 58 | .param("list[0].name", "list-name")); 59 | 60 | assertThat(spyDemoService.mapFrom_argumentModelAttribute.getId()).isEqualTo("id1"); 61 | assertThat(spyDemoService.mapFrom_argumentModelAttribute.getName()).isEqualTo("name1"); 62 | assertThat(spyDemoService.mapFrom_argumentModelAttribute.getList().get(0).getId()).isEqualTo("list-id"); 63 | assertThat(spyDemoService.mapFrom_argumentModelAttribute.getList().get(0).getName()).isEqualTo("list-name"); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /spring-object-binding/src/test/java/com/zkdlu/binding/SpyDemoService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.binding; 2 | 3 | import com.zkdlu.binding.BodyData.BodyDataRequest; 4 | import com.zkdlu.binding.BodyData.BodyDataResponse; 5 | import com.zkdlu.binding.ModelAttributeData.ModelAttributeRequest; 6 | 7 | public class SpyDemoService implements DemoService { 8 | public BodyDataRequest mapFrom_argumentRequestBody; 9 | public ModelAttributeRequest mapFrom_argumentModelAttribute; 10 | 11 | @Override 12 | public BodyDataResponse mapFrom(BodyDataRequest request) { 13 | mapFrom_argumentRequestBody = request; 14 | return request.toResponse(); 15 | } 16 | 17 | @Override 18 | public ModelAttributeData.ModelAttributeResponse mapFrom(ModelAttributeRequest model) { 19 | mapFrom_argumentModelAttribute = model; 20 | return null; 21 | } 22 | } -------------------------------------------------------------------------------- /spring-payment/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 3 | implementation 'org.springframework.boot:spring-boot-starter-web' 4 | 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | 8 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 9 | runtimeOnly 'com.h2database:h2' 10 | } -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/KakaoPayApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu; 2 | 3 | import com.zkdlu.domain.product.Product; 4 | import com.zkdlu.domain.product.ProductRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.ApplicationArguments; 7 | import org.springframework.boot.ApplicationRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | @SpringBootApplication 14 | public class KakaoPayApplication implements ApplicationRunner { 15 | public static void main(String[] args) { 16 | SpringApplication.run(KakaoPayApplication.class, args); 17 | } 18 | 19 | @Bean 20 | public RestTemplate restTemplate() { 21 | return new RestTemplate(); 22 | } 23 | 24 | @Autowired 25 | ProductRepository productRepository; 26 | 27 | @Override 28 | public void run(ApplicationArguments args) throws Exception { 29 | for (int i = 0; i < 100; i++) { 30 | productRepository.save(Product.builder() 31 | .name("옷" + i) 32 | .image("https://placeimg.com/100/100/any") 33 | .price(1000 + i * 1000) 34 | .stock(i) 35 | .build()); 36 | productRepository.save(Product.builder() 37 | .name("신발" + i) 38 | .price(1000 + i * 1000) 39 | .stock(i) 40 | .image("https://placeimg.com/100/100/any") 41 | .build()); 42 | productRepository.save(Product.builder() 43 | .name("장난감" + i) 44 | .image("https://placeimg.com/100/100/any") 45 | .price(1000 + i * 1000) 46 | .stock(i) 47 | .build()); 48 | 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/advice/ExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.advice; 2 | 3 | import org.springframework.web.bind.annotation.ExceptionHandler; 4 | import org.springframework.web.bind.annotation.RestControllerAdvice; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | @RestControllerAdvice 9 | public class ExceptionAdvice { 10 | @ExceptionHandler(IllegalStateException.class) 11 | public String handle(HttpServletRequest request, Exception e) { 12 | return "재고없음"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/api/order/Cart.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.api.order; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Getter 10 | @NoArgsConstructor 11 | class Cart { 12 | private List items = new ArrayList<>(); 13 | } -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/api/order/CartItem.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.api.order; 2 | 3 | import com.zkdlu.domain.order.OrderItem; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor 9 | class CartItem { 10 | private Long id; 11 | private String name; 12 | private int price; 13 | 14 | OrderItem toOrderItem() { 15 | return new OrderItem(id, name, price); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/api/order/OrderApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.api.order; 2 | 3 | import com.zkdlu.domain.order.OrderItem; 4 | import com.zkdlu.domain.order.OrderService; 5 | import com.zkdlu.domain.order.PayRequest; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import javax.servlet.http.HttpSession; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @RequiredArgsConstructor 16 | @RestController 17 | public class OrderApi { 18 | private final OrderService orderService; 19 | 20 | @PostMapping("/order") 21 | public PayRequest order(@RequestBody Cart cart, HttpSession session) { 22 | List orderItems = cart.getItems() 23 | .stream() 24 | .map(CartItem::toOrderItem) 25 | .collect(Collectors.toList()); 26 | 27 | PayRequest payRequest = orderService.placeOrder(orderItems); 28 | 29 | session.setAttribute("payReady", payRequest.getPayReady()); 30 | 31 | return payRequest; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/api/payment/PaymentApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.api.payment; 2 | 3 | import com.zkdlu.domain.order.Order; 4 | import com.zkdlu.domain.payment.KakaoPay; 5 | import com.zkdlu.domain.payment.remote.PayReady; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpSession; 17 | import java.io.IOException; 18 | 19 | @Slf4j 20 | @RequiredArgsConstructor 21 | @RequestMapping("/pay") 22 | @RestController 23 | public class PaymentApi { 24 | private final KakaoPay kakaopay; 25 | 26 | @PostMapping 27 | public PayReady pay(@RequestBody Order order, HttpServletRequest request) throws IOException { 28 | var payReady = kakaopay.prepare(order); 29 | log.info(payReady.getTid()); 30 | 31 | var session = request.getSession(true); 32 | session.setAttribute(order.getId().toString(), payReady); 33 | 34 | return payReady; 35 | } 36 | 37 | 38 | @GetMapping("/success") 39 | public String paySuccess(@RequestParam("pg_token") String pg_token, 40 | @RequestParam("order") Long orderId, 41 | HttpSession session) { 42 | log.info("paySuccess pg_token : " + pg_token); 43 | 44 | var payReady = (PayReady)session.getAttribute("payReady"); 45 | 46 | return kakaopay.approve(pg_token, orderId, payReady); 47 | } 48 | 49 | @GetMapping("/cancel") 50 | public String payCancel() { 51 | log.info("payCancel get............................................"); 52 | 53 | return "cancel"; 54 | } 55 | 56 | @GetMapping("/fail") 57 | public String payFail() { 58 | log.info("payFail get............................................"); 59 | 60 | return "fail"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/api/product/ProductApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.api.product; 2 | 3 | import com.zkdlu.domain.product.Product; 4 | import com.zkdlu.domain.product.ProductService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | @RestController 14 | public class ProductApi { 15 | private final ProductService productService; 16 | 17 | @GetMapping("/products") 18 | public List getProducts() { 19 | return productService.getProducts(); 20 | } 21 | 22 | @GetMapping("/products/page/{page}") 23 | public List getProductPage(@PathVariable int page) { 24 | return productService.getProductsByPage(page); 25 | } 26 | 27 | @GetMapping("/products/detail/{id}") 28 | public Product getProduct(@PathVariable Long id) { 29 | return productService.getProductDetail(id); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/Order.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Entity; 9 | import javax.persistence.EnumType; 10 | import javax.persistence.Enumerated; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.JoinColumn; 15 | import javax.persistence.OneToMany; 16 | import javax.persistence.Table; 17 | import java.util.List; 18 | 19 | @Entity 20 | @Getter 21 | @NoArgsConstructor 22 | @Table(name = "ORDERS") 23 | public class Order { 24 | public enum State { 25 | PREPARE, PAYED, COMPLETE 26 | } 27 | 28 | @Id 29 | @GeneratedValue(strategy = GenerationType.AUTO) 30 | private Long id; 31 | @OneToMany(cascade = CascadeType.ALL) 32 | @JoinColumn(name = "ORDER_ID") 33 | private List orderItems; 34 | @Enumerated(value = EnumType.STRING) 35 | private State state; 36 | 37 | @Builder 38 | public Order(List orderItems) { 39 | this.orderItems = orderItems; 40 | this.state = State.PREPARE; 41 | } 42 | 43 | public void payed() { 44 | this.state = State.PAYED; 45 | } 46 | 47 | private void verify() { 48 | for (OrderItem orderItem : orderItems) { 49 | orderItem.verify(); 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/OrderEvent.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import lombok.Getter; 4 | import org.springframework.context.ApplicationEvent; 5 | 6 | @Getter 7 | public class OrderEvent extends ApplicationEvent { 8 | private Long orderId; 9 | /** 10 | * Create a new {@code ApplicationEvent}. 11 | * 12 | * @param source the object on which the event initially occurred or with 13 | * which the event is associated (never {@code null}) 14 | */ 15 | public OrderEvent(Object source, Long orderId) { 16 | super(source); 17 | 18 | this.orderId = orderId; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/OrderEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.context.event.EventListener; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Slf4j 9 | @RequiredArgsConstructor 10 | @Component 11 | public class OrderEventHandler { 12 | 13 | @EventListener 14 | public void listen(OrderEvent orderEvent) { 15 | log.info("{}", orderEvent.getOrderId()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | 10 | @Getter 11 | @NoArgsConstructor 12 | @Entity 13 | public class OrderItem { 14 | @Id 15 | private Long id; 16 | private String name; 17 | private int price; 18 | 19 | @Builder 20 | public OrderItem(Long id, String name, int price) { 21 | this.id = id; 22 | this.name = name; 23 | this.price = price; 24 | } 25 | 26 | private boolean remainStock; 27 | 28 | public void canBuy(int stock) { 29 | remainStock = stock > 0; 30 | } 31 | 32 | public void verify() { 33 | if (!remainStock) { 34 | throw new IllegalStateException("재고 없음"); 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface OrderRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import com.zkdlu.domain.payment.KakaoPay; 4 | import com.zkdlu.domain.payment.remote.PayReady; 5 | import com.zkdlu.domain.product.Product; 6 | import com.zkdlu.domain.product.ProductService; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.context.ApplicationEventPublisher; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | @Slf4j 16 | @RequiredArgsConstructor 17 | @Service 18 | public class OrderService { 19 | private final ApplicationEventPublisher eventPublisher; 20 | private final ProductService productService; 21 | private final OrderRepository orderRepository; 22 | private final KakaoPay kakaoPay; 23 | 24 | @Transactional 25 | public PayRequest placeOrder(List orderItems) { 26 | log.info("order service: {}", Thread.currentThread().getId()); 27 | 28 | checkCanBuyProduct(orderItems); 29 | 30 | Order order = Order.builder() 31 | .orderItems(orderItems) 32 | .build(); 33 | orderRepository.save(order); 34 | 35 | eventPublisher.publishEvent(new OrderEvent(this, order.getId())); 36 | 37 | PayReady payReady = kakaoPay.prepare(order); 38 | 39 | return PayRequest.builder() 40 | .payReady(payReady) 41 | .order(order) 42 | .build(); 43 | } 44 | 45 | private void checkCanBuyProduct(List orderItems) { 46 | for (OrderItem orderItem : orderItems) { 47 | Product product = productService.getProductDetail(orderItem.getId()); 48 | orderItem.canBuy(product.getStock()); 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/order/PayRequest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.order; 2 | 3 | import com.zkdlu.domain.payment.remote.PayReady; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | public class PayRequest { 11 | private Order order; 12 | private PayReady payReady; 13 | 14 | @Builder 15 | public PayRequest(Order order, PayReady payReady) { 16 | this.order = order; 17 | this.payReady = payReady; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/payment/KakaoPayReadyVO.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.payment; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.util.Date; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | public class KakaoPayReadyVO { 11 | private String tid; 12 | private String next_redirect_pc_url; 13 | private Date created_at; 14 | } 15 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/payment/Payment.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.payment; 2 | 3 | import com.zkdlu.domain.order.Order; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.EnumType; 10 | import javax.persistence.Enumerated; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.JoinColumn; 15 | import javax.persistence.OneToOne; 16 | import javax.persistence.Table; 17 | 18 | @Getter 19 | @NoArgsConstructor 20 | @Entity 21 | @Table(name = "PAYMENTS") 22 | public class Payment { 23 | 24 | public enum State { 25 | PREPARE, PAYED, COMPLETE 26 | } 27 | 28 | @Id 29 | @GeneratedValue(strategy = GenerationType.AUTO) 30 | private Long id; 31 | @Enumerated(value = EnumType.STRING) 32 | private State state; 33 | @OneToOne 34 | @JoinColumn(name = "ORDER_ID") 35 | private Order order; 36 | 37 | @Builder 38 | public Payment(Long id, Order order) { 39 | this.id = id; 40 | this.order = order; 41 | this.state = State.PREPARE; 42 | } 43 | 44 | public void payed() { 45 | this.state = State.PAYED; 46 | 47 | order.payed(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/payment/PaymentRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.payment; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface PaymentRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/payment/remote/PayReady.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.payment.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Date; 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Getter 13 | public class PayReady { 14 | @JsonProperty("tid") 15 | private String tid; 16 | @JsonProperty("next_redirect_pc_url") 17 | private String nextRedirectPcUrl; 18 | @JsonProperty("created_at") 19 | private Date createdAt; 20 | } -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.product; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.GenerationType; 10 | import javax.persistence.Id; 11 | 12 | @Entity 13 | @Getter 14 | @NoArgsConstructor 15 | public class Product { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.AUTO) 18 | private Long id; 19 | private String name; 20 | private String image; 21 | private int price; 22 | private int stock; 23 | 24 | @Builder 25 | public Product(Long id, String name, String image, int price, int stock) { 26 | this.id = id; 27 | this.name = name; 28 | this.image = image; 29 | this.price = price; 30 | this.stock = stock; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.product; 2 | 3 | 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | public interface ProductRepository extends JpaRepository { 9 | Page findAll(Pageable paging); 10 | } 11 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/domain/product/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.domain.product; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | 9 | @RequiredArgsConstructor 10 | @Service 11 | public class ProductService { 12 | private final ProductRepository productRepository; 13 | 14 | public List getProducts() { 15 | return productRepository.findAll(); 16 | } 17 | 18 | public List getProductsByPage(int page) { 19 | return productRepository.findAll(PageRequest.of(page, 42)) 20 | .toList(); 21 | } 22 | 23 | public Product getProductDetail(Long id) { 24 | return productRepository.findById(id) 25 | .orElseThrow(IllegalAccessError::new); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-payment/src/main/java/com/zkdlu/web/WebController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.web; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | 8 | @Controller 9 | public class WebController { 10 | @GetMapping 11 | public String index() { 12 | return "index.html"; 13 | } 14 | 15 | @GetMapping("/order/{productId}") 16 | public String order(Model model, @PathVariable String productId) { 17 | model.addAttribute("productId", productId); 18 | 19 | return "order.html"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-payment/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:h2:mem:test-db 4 | driver-class-name: org.h2.Driver 5 | username: sa 6 | password: 7 | jpa: 8 | database-platform: org.hibernate.dialect.H2Dialect 9 | database: h2 10 | show-sql: true 11 | hibernate: 12 | ddl-auto: create 13 | thymeleaf: 14 | cache: false 15 | h2: 16 | console: 17 | enabled: true 18 | -------------------------------------------------------------------------------- /spring-payment/src/main/resources/static/images/payment_icon_yellow_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkdlu/spring-boot/6790c634fcd147659a856b4bf5b764af387a700a/spring-payment/src/main/resources/static/images/payment_icon_yellow_large.png -------------------------------------------------------------------------------- /spring-querydsl/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url "https://plugins.gradle.org/m2/" } 3 | } 4 | 5 | dependencies { 6 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 7 | runtimeOnly 'com.h2database:h2' 8 | compileOnly 'org.projectlombok:lombok' 9 | annotationProcessor 'org.projectlombok:lombok' 10 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 11 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 12 | } 13 | 14 | implementation("com.querydsl:querydsl-core") // querydsl 15 | implementation("com.querydsl:querydsl-jpa") // querydsl 16 | annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" // querydsl JPAAnnotationProcessor 사용 지정 17 | annotationProcessor("jakarta.persistence:jakarta.persistence-api") 18 | annotationProcessor("jakarta.annotation:jakarta.annotation-api") 19 | } 20 | 21 | def generated='src/main/generated' 22 | sourceSets { 23 | main.java.srcDirs += [ generated ] 24 | } 25 | 26 | tasks.withType(JavaCompile) { 27 | options.annotationProcessorGeneratedSourcesDirectory = file(generated) 28 | } 29 | 30 | clean.doLast { 31 | file(generated).deleteDir() 32 | } 33 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/generated/com/zkdlu/querydsl/domain/QPerson.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl.domain; 2 | 3 | import static com.querydsl.core.types.PathMetadataFactory.*; 4 | 5 | import com.querydsl.core.types.dsl.*; 6 | 7 | import com.querydsl.core.types.PathMetadata; 8 | import javax.annotation.Generated; 9 | import com.querydsl.core.types.Path; 10 | 11 | 12 | /** 13 | * QPerson is a Querydsl query type for Person 14 | */ 15 | @Generated("com.querydsl.codegen.EntitySerializer") 16 | public class QPerson extends EntityPathBase { 17 | 18 | private static final long serialVersionUID = 2074619141L; 19 | 20 | public static final QPerson person = new QPerson("person"); 21 | 22 | public final NumberPath id = createNumber("id", Long.class); 23 | 24 | public final StringPath name = createString("name"); 25 | 26 | public QPerson(String variable) { 27 | super(Person.class, forVariable(variable)); 28 | } 29 | 30 | public QPerson(Path path) { 31 | super(path.getType(), path.getMetadata()); 32 | } 33 | 34 | public QPerson(PathMetadata metadata) { 35 | super(Person.class, metadata); 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/java/com/zkdlu/querydsl/QuerydslApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl; 2 | 3 | import org.springframework.boot.ApplicationArguments; 4 | import org.springframework.boot.ApplicationRunner; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | public class QuerydslApplication implements ApplicationRunner { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(QuerydslApplication.class, args); 13 | } 14 | 15 | 16 | 17 | @Override 18 | public void run(ApplicationArguments args) throws Exception { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/java/com/zkdlu/querydsl/config/QuerydslConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl.config; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | 10 | @Configuration 11 | public class QuerydslConfiguration { 12 | @PersistenceContext 13 | private EntityManager entityManager; 14 | 15 | @Bean 16 | public JPAQueryFactory jpaQueryFactory() { 17 | return new JPAQueryFactory(entityManager); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/java/com/zkdlu/querydsl/domain/Person.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl.domain; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.GenerationType; 10 | import javax.persistence.Id; 11 | 12 | @Getter 13 | @NoArgsConstructor 14 | @Entity 15 | public class Person { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private long id; 19 | private String name; 20 | 21 | @Builder 22 | public Person(String name) { 23 | this.name = name; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/java/com/zkdlu/querydsl/domain/PersonQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl.domain; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | import static com.zkdlu.querydsl.domain.QPerson.person; 10 | 11 | @RequiredArgsConstructor 12 | @Repository 13 | public class PersonQueryRepository { 14 | private final JPAQueryFactory queryFactory; 15 | 16 | public List findByName(String name) { 17 | return queryFactory.selectFrom(person) 18 | .where(person.name.eq(name)) 19 | .fetch(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/java/com/zkdlu/querydsl/domain/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl.domain; 2 | 3 | 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface PersonRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /spring-querydsl/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:h2:mem:test-db 4 | driver-class-name: org.h2.Driver 5 | username: sa 6 | password: 7 | jpa: 8 | database-platform: org.hibernate.dialect.H2Dialect 9 | database: h2 10 | show-sql: true 11 | hibernate: 12 | ddl-auto: update -------------------------------------------------------------------------------- /spring-querydsl/src/test/java/com/zkdlu/querydsl/QuerydslApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class QuerydslApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-querydsl/src/test/java/com/zkdlu/querydsl/domain/PersonQueryRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.querydsl.domain; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit.jupiter.SpringExtension; 9 | 10 | import java.util.List; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @ExtendWith(SpringExtension.class) 15 | @SpringBootTest 16 | class PersonQueryRepositoryTest { 17 | @Autowired 18 | private PersonRepository personRepository; 19 | @Autowired 20 | private PersonQueryRepository personQueryRepository; 21 | 22 | @Test 23 | @DisplayName("QueryDSL 테스트") 24 | public void queryDsl_Test() { 25 | //given 26 | String name = "geon"; 27 | personRepository.save(new Person(name)); 28 | 29 | //when 30 | List result = personQueryRepository.findByName(name); 31 | 32 | //then 33 | assertThat(result.size()).isEqualTo(1); 34 | } 35 | } -------------------------------------------------------------------------------- /spring-querydsl/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:h2:mem:test-db 4 | driver-class-name: org.h2.Driver 5 | username: sa 6 | password: 7 | jpa: 8 | database-platform: org.hibernate.dialect.H2Dialect 9 | database: h2 10 | show-sql: true 11 | hibernate: 12 | ddl-auto: update -------------------------------------------------------------------------------- /spring-scheduled/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation 'org.springframework.boot:spring-boot-starter' 4 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 5 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 6 | } 7 | } -------------------------------------------------------------------------------- /spring-scheduled/src/main/java/com/zkdlu/scheduled/ScheduledApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.scheduled; 2 | 3 | import org.springframework.boot.ApplicationArguments; 4 | import org.springframework.boot.ApplicationRunner; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | 9 | @EnableScheduling 10 | @SpringBootApplication 11 | public class ScheduledApplication implements ApplicationRunner { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(ScheduledApplication.class, args); 15 | } 16 | 17 | @Override 18 | public void run(ApplicationArguments args) throws Exception { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-scheduled/src/main/java/com/zkdlu/scheduled/Scheduler.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.scheduled; 2 | 3 | import org.springframework.scheduling.annotation.Scheduled; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | @Component 9 | public class Scheduler { 10 | 11 | /* 12 | second minute hour days month week 13 | 0-59 0-59 0-23 1-31 1-12 0-7 14 | */ 15 | @Scheduled(cron = "* * * * * *") 16 | public void cronJob(String test) { 17 | System.out.println("cron : " + LocalDateTime.now().toString()); 18 | } 19 | 20 | @Scheduled(fixedDelay = 1000) 21 | public void delayJob() { 22 | System.out.println("delay : " + LocalDateTime.now().toString()); 23 | } 24 | 25 | @Scheduled(fixedRate = 1000) 26 | public void rateJob() { 27 | System.out.println("rate : " + LocalDateTime.now().toString()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-scheduled/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spring-scheduled/src/test/java/com/zkdlu/scheduled/ScheduledApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.scheduled; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ScheduledApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-scheduled/src/test/java/com/zkdlu/scheduled/SchedulerTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.scheduled; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class SchedulerTest { 6 | @Test 7 | void 아아() { 8 | //given 9 | 10 | //when 11 | 12 | //then 13 | } 14 | } -------------------------------------------------------------------------------- /spring-service/display-service/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation 'org.springframework.kafka:spring-kafka' 4 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 5 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon' 6 | implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' 7 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix' 8 | } 9 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/DisplayApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 8 | import org.springframework.cloud.openfeign.EnableFeignClients; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @EnableEurekaClient 13 | @EnableCircuitBreaker 14 | @EnableFeignClients 15 | @SpringBootApplication 16 | public class DisplayApplication { 17 | @Bean 18 | @LoadBalanced 19 | public RestTemplate restTemplate() { 20 | return new RestTemplate(); 21 | } 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(DisplayApplication.class, args); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/DisplayController.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import java.util.List; 8 | 9 | @RestController 10 | public class DisplayController { 11 | private final DisplayService displayService; 12 | 13 | public DisplayController(DisplayService displayService) { 14 | this.displayService = displayService; 15 | } 16 | 17 | @GetMapping("/display") 18 | public List getProducts() { 19 | return displayService.getProducts(); 20 | } 21 | 22 | @GetMapping("/order/{productId}") 23 | public String orderProduct(@PathVariable String productId) { 24 | return displayService.orderProduct(productId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/DisplayService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | @Service 12 | public class DisplayService { 13 | @Value("${kafka.topics.test}") 14 | private String topic; 15 | 16 | private final Producer producer; 17 | private final RestTemplate restTemplate; 18 | private final FeignProductService productProxy; 19 | 20 | public DisplayService(Producer producer, RestTemplate restTemplate, FeignProductService productProxy) { 21 | this.producer = producer; 22 | this.restTemplate = restTemplate; 23 | this.productProxy = productProxy; 24 | } 25 | 26 | @HystrixCommand(fallbackMethod = "getProductsFallback") 27 | public List getProducts() { 28 | return productProxy.getProducts(); 29 | } 30 | 31 | public List getProductsFallback() { 32 | return Collections.singletonList(Product.Empty); 33 | } 34 | 35 | public String orderProduct(String productId) { 36 | producer.sendMessage(topic, productId); 37 | 38 | return productId; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/FeignProductService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | 7 | import java.util.List; 8 | 9 | @FeignClient(name = "product", 10 | fallback = FeignProductServiceFallbackImpl.class) 11 | public interface FeignProductService { 12 | @RequestMapping(path = "/products") 13 | List getProducts(); 14 | 15 | @RequestMapping(path = "/order/{productId}") 16 | Product orderProduct(@PathVariable("productId") String productId); 17 | } 18 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/FeignProductServiceFallbackImpl.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | @Component 9 | public class FeignProductServiceFallbackImpl implements FeignProductService{ 10 | @Override 11 | public List getProducts() { 12 | return Collections.singletonList(Product.Empty); 13 | } 14 | 15 | @Override 16 | public Product orderProduct(String productId) { 17 | return Product.Empty; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/KafkaProducerConfig.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import org.apache.kafka.common.serialization.StringSerializer; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.kafka.annotation.EnableKafka; 8 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 9 | import org.springframework.kafka.core.KafkaTemplate; 10 | import org.springframework.kafka.core.ProducerFactory; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | @EnableKafka 16 | @Configuration 17 | public class KafkaProducerConfig { 18 | @Value("${spring.kafka.bootstrap-servers}") 19 | private String bootstrapServers; 20 | 21 | @Bean 22 | public Map producerConfig() { 23 | Map props = new HashMap<>(); 24 | props.put(org.apache.kafka.clients.producer.ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); 25 | props.put(org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 26 | props.put(org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 27 | return props; 28 | } 29 | 30 | @Bean 31 | public ProducerFactory producerFactory() { 32 | return new DefaultKafkaProducerFactory<>(producerConfig()); 33 | } 34 | 35 | @Bean 36 | public KafkaTemplate kafkaTemplate() { 37 | return new KafkaTemplate<>(producerFactory()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/Producer.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | import org.springframework.kafka.core.KafkaTemplate; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class Producer { 8 | private final KafkaTemplate kafkaTemplate; 9 | 10 | public Producer(KafkaTemplate kafkaTemplate) { 11 | this.kafkaTemplate = kafkaTemplate; 12 | } 13 | 14 | public void sendMessage(String topic, String message) { 15 | kafkaTemplate.send(topic, message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/java/com/zkdlu/display/Product.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.display; 2 | 3 | public class Product { 4 | public static final Product Empty = new Product("0", "[Sold out]", 0, 0); 5 | 6 | private String id; 7 | private String name; 8 | private int price; 9 | private int stock; 10 | 11 | public Product() { 12 | } 13 | 14 | public Product(String id, String name, int price, int stock) { 15 | this.id = id; 16 | this.name = name; 17 | this.price = price; 18 | this.stock = stock; 19 | } 20 | 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public int getPrice() { 30 | return price; 31 | } 32 | 33 | public int getStock() { 34 | return stock; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spring-service/display-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | feign: 2 | hystrix: 3 | enabled: true 4 | 5 | hystrix: 6 | command: 7 | FeignProductService#getProducts(): 8 | execution: 9 | isolation: 10 | thread: 11 | timeoutInMilliseconds: 1000 12 | circuitBreaker: 13 | requestVolumeThreshold: 1 # 감시 시간 내 요청 수, 기본값 20 14 | errorThresholdPercentage: 50 # 요청 대비 오류율, 기본값 50 15 | default: 16 | execution: 17 | isolation: 18 | thread: 19 | timeoutInMilliseconds: 3000 20 | circuitBreaker: 21 | requestVolumeThreshold: 1 # 감시 시간 내 요청 수, 기본값 20 22 | errorThresholdPercentage: 50 # 요청 대비 오류율, 기본값 50 23 | 24 | product: 25 | ribbon: 26 | ConnectTimeout: 3000 27 | 28 | eureka: 29 | instance: 30 | prefer-ip-address: true 31 | client: 32 | service-url: 33 | defaultZone: http://localhost:8761/eureka 34 | 35 | spring: 36 | application: 37 | name: display 38 | kafka: 39 | consumer: 40 | group-id: myGroup 41 | bootstrap-servers: localhost:9092 42 | 43 | kafka: 44 | topics: 45 | test: order -------------------------------------------------------------------------------- /spring-service/eureka-server/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' 3 | } 4 | -------------------------------------------------------------------------------- /spring-service/eureka-server/src/main/java/com/zkdlu/eureka/EurekaApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.eureka; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class EurekaApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(EurekaApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-service/eureka-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | spring: 5 | application: 6 | name: eureka-server 7 | 8 | eureka: 9 | server: 10 | response-cache-update-interval-ms: 1000 # Default 30,000ms 11 | client: 12 | register-with-eureka: false # Only for local stand-alone development 13 | fetch-registry: false # Only for local stand-alone development 14 | service-url: 15 | defaultZone: http://localhost:8761/eureka # Default Value. Just for demo -------------------------------------------------------------------------------- /spring-service/product-service/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 4 | implementation 'org.springframework.kafka:spring-kafka' 5 | } 6 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class Consumer { 11 | private final Logger log = LoggerFactory.getLogger(Consumer.class); 12 | private final ProductService productService; 13 | 14 | public Consumer(ProductService productService) { 15 | this.productService = productService; 16 | } 17 | 18 | @Value("${kafka.topics.test}") 19 | private String topic; 20 | 21 | @KafkaListener(topics = "${kafka.topics.test}", groupId = "${spring.kafka.consumer.group-id}") 22 | void listen(String message) { 23 | productService.orderProdcut(message); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/KafkaConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerConfig; 4 | import org.apache.kafka.common.serialization.StringDeserializer; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 9 | import org.springframework.kafka.config.KafkaListenerContainerFactory; 10 | import org.springframework.kafka.core.ConsumerFactory; 11 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 12 | import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Configuration 18 | public class KafkaConsumerConfig { 19 | @Value("${spring.kafka.bootstrap-servers}") 20 | private String bootstrapServers; 21 | 22 | @Bean 23 | public Map consumerConfig() { 24 | Map props = new HashMap<>(); 25 | props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); 26 | props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 27 | props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 28 | return props; 29 | } 30 | 31 | @Bean 32 | public ConsumerFactory consumerFactory() { 33 | return new DefaultKafkaConsumerFactory<>(consumerConfig()); 34 | } 35 | 36 | @Bean 37 | public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { 38 | ConcurrentKafkaListenerContainerFactory factory = 39 | new ConcurrentKafkaListenerContainerFactory<>(); 40 | factory.setConsumerFactory(consumerFactory()); 41 | return factory; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | public class Product { 4 | private String id; 5 | private String name; 6 | private int price; 7 | private int stock; 8 | 9 | public Product(String id, int price, int stock) { 10 | this.id = id; 11 | this.name = "product-" + id; 12 | this.price = price; 13 | this.stock = stock; 14 | } 15 | 16 | public String getId() { 17 | return id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public int getPrice() { 25 | return price; 26 | } 27 | 28 | public int getStock() { return stock; } 29 | 30 | public void order(int count) { 31 | if (stock - count < 0) { 32 | throw new IllegalStateException("재고가 부족합니다."); 33 | } 34 | 35 | stock -= count; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/ProductApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import java.util.List; 8 | 9 | @RestController 10 | public class ProductApi { 11 | private final ProductService productService; 12 | 13 | public ProductApi(ProductService productService) { 14 | this.productService = productService; 15 | } 16 | 17 | @GetMapping("/products") 18 | public List getProducts() { 19 | return productService.getProducts(); 20 | } 21 | 22 | @GetMapping("/order/{productId}") 23 | public Product orderProduct(@PathVariable String productId) { 24 | return productService.orderProdcut(productId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/ProductApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @EnableEurekaClient 8 | @SpringBootApplication 9 | public class ProductApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(ProductApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public class ProductRepository { 11 | private final List products = Arrays.asList(new Product("1", 1000, 10), 12 | new Product("2", 1500, 10), 13 | new Product("3", 2000, 10)); 14 | 15 | public List findAll() { 16 | return products; 17 | } 18 | 19 | public Optional findById(String productId) { 20 | return products.stream().filter(i -> i.getId().equals(productId)).findFirst(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/java/com/zkdlu/product/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.util.List; 6 | 7 | @Service 8 | public class ProductService { 9 | private final ProductRepository productRepository; 10 | 11 | public ProductService(ProductRepository productRepository) { 12 | this.productRepository = productRepository; 13 | } 14 | 15 | public List getProducts() { 16 | return productRepository.findAll(); 17 | } 18 | 19 | public Product orderProdcut(String productId) { 20 | var product = productRepository.findById(productId) 21 | .orElseThrow(IllegalAccessError::new); 22 | 23 | product.order(1); 24 | 25 | return product; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-service/product-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | eureka: 5 | instance: 6 | prefer-ip-address: true 7 | client: 8 | service-url: 9 | defaultZone: http://localhost:8761/eureka 10 | 11 | spring: 12 | application: 13 | name: product 14 | kafka: 15 | consumer: 16 | group-id: myGroup 17 | bootstrap-servers: localhost:9092 18 | 19 | kafka: 20 | topics: 21 | test: order -------------------------------------------------------------------------------- /spring-service/product-service/src/test/java/com/zkdlu/product/ProductTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.product; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class ProductTest { 10 | 11 | @Test 12 | @DisplayName("주문수량 확인") 13 | void 주문수량_확인() { 14 | //given 15 | Product product = new Product("1", 1000, 10); 16 | //when 17 | product.order(5); 18 | //then 19 | assertThat(product.getStock()).isEqualTo(5); 20 | } 21 | 22 | @Test 23 | @DisplayName("초과주문") 24 | void 초과주문() { 25 | //given 26 | Product product = new Product("1", 1000, 10); 27 | 28 | //when 29 | var exception = assertThrows(IllegalStateException.class, 30 | () -> product.order(11)); 31 | 32 | //then 33 | } 34 | } -------------------------------------------------------------------------------- /spring-slack/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'com.google.code.gson:gson:2.8.6' 3 | implementation 'com.squareup.okhttp3:okhttp:4.2.0' 4 | implementation 'com.slack.api:slack-app-backend:1.6.2' 5 | implementation 'com.slack.api:slack-api-model:1.6.2' 6 | implementation 'com.slack.api:slack-api-client:1.6.2' 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 9 | } -------------------------------------------------------------------------------- /spring-slack/src/main/java/com/zkdlu/slacker/SlackApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.slacker; 2 | 3 | import com.slack.api.app_backend.interactive_components.payload.BlockActionPayload; 4 | import com.slack.api.util.json.GsonFactory; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.io.IOException; 13 | 14 | @RestController 15 | public class SlackApi { 16 | private final SlackService slackService; 17 | private final SlackBot slackBot; 18 | 19 | public SlackApi(SlackService slackService, SlackBot slackBot) { 20 | this.slackService = slackService; 21 | this.slackBot = slackBot; 22 | } 23 | 24 | @GetMapping("/vote/{message}") 25 | public String test(@PathVariable String message) throws IOException { 26 | return slackBot.vote(message); 27 | } 28 | 29 | @PostMapping(value = "/slack/callback", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) 30 | public String callback(@RequestParam String payload) throws IOException { 31 | var blockPayload = GsonFactory.createSnakeCase() 32 | .fromJson(payload, BlockActionPayload.class); 33 | 34 | return slackBot.callbackVote(blockPayload); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spring-slack/src/main/java/com/zkdlu/slacker/SlackerApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.slacker; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | @SpringBootApplication 9 | public class SlackerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SlackerApplication.class, args); 13 | } 14 | 15 | @Bean 16 | public RestTemplate restTemplate() { 17 | return new RestTemplate(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-slack/src/main/java/com/zkdlu/slacker/TestApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.slacker; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class TestApi { 9 | private final SlackService slackService; 10 | 11 | public TestApi(SlackService slackService) { 12 | this.slackService = slackService; 13 | } 14 | 15 | @GetMapping("/plane/{text}") 16 | public String knock(@PathVariable String text) { 17 | slackService.sendPlane(text); 18 | 19 | return "knock knock"; 20 | } 21 | 22 | @GetMapping("/link/{text}") 23 | public String link(@PathVariable String text) { 24 | slackService.sendLink(text); 25 | 26 | return "knock knock link"; 27 | } 28 | 29 | @GetMapping("/emoji/{text}") 30 | public String custom(@PathVariable String text) { 31 | slackService.sendEmoji(text); 32 | 33 | return "knock knock emoji"; 34 | } 35 | 36 | @GetMapping("/icon/{text}") 37 | public String icon(@PathVariable String text) { 38 | slackService.sendIcon(text); 39 | 40 | return "knock knock icon"; 41 | } 42 | 43 | @GetMapping("/attach/{text}") 44 | public String attach(@PathVariable String text) { 45 | slackService.sendAttatchments(text); 46 | 47 | return "knock knock attach"; 48 | } 49 | 50 | @GetMapping("/button/{text}") 51 | public String button(@PathVariable String text) { 52 | slackService.sendButtons(text); 53 | 54 | return "knock knock attach"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-slack/src/main/java/com/zkdlu/slacker/Vote.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.slacker; 2 | 3 | public class Vote { 4 | private String user; 5 | private String actionId; 6 | 7 | public Vote(String user, String actionId) { 8 | this.user = user; 9 | this.actionId = actionId; 10 | } 11 | 12 | public String getUser() { 13 | return user; 14 | } 15 | 16 | public String getActionId() { 17 | return actionId; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | Vote vote = (Vote)o; 23 | return vote.getUser().equals(user); 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return user.hashCode(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spring-slack/src/test/java/com/zkdlu/slacker/SlackerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.slacker; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SlackerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-tdd/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 3 | implementation 'org.springframework.boot:spring-boot-starter-web' 4 | runtimeOnly 'com.h2database:h2' 5 | compileOnly 'org.projectlombok:lombok' 6 | annotationProcessor 'org.projectlombok:lombok' 7 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 8 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 9 | } 10 | } -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/TddApplication.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd; 2 | 3 | import org.springframework.boot.ApplicationArguments; 4 | import org.springframework.boot.ApplicationRunner; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | public class TddApplication implements ApplicationRunner { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(TddApplication.class, args); 13 | } 14 | 15 | @Override 16 | public void run(ApplicationArguments args) throws Exception { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/api/OrderApi.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.api; 2 | 3 | import com.zkdlu.tdd.service.OrderDto; 4 | import com.zkdlu.tdd.service.OrderService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RequiredArgsConstructor 11 | @RestController 12 | public class OrderApi { 13 | private final OrderService orderService; 14 | 15 | @PostMapping 16 | public String placeOrder(@RequestBody OrderDto orderDto) { 17 | return orderService.placeOrder(orderDto); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/domain/order/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.domain.order; 2 | 3 | import com.zkdlu.tdd.domain.shop.Menu; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Id; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.ManyToOne; 12 | import javax.persistence.Table; 13 | 14 | @Getter 15 | @NoArgsConstructor 16 | @Entity 17 | @Table(name = "ORDERITEMS") 18 | public class OrderItem { 19 | @Id 20 | @Column(name = "ORDER_ITEM_ID") 21 | private String id; 22 | @Column(name = "ORDER_ITEM_NAME") 23 | private String name; 24 | @ManyToOne 25 | @JoinColumn(name = "MENU_ID") 26 | private Menu menu; 27 | @Column(name = "COUNT") 28 | private int count; 29 | 30 | public OrderItem(Menu menu) { 31 | this.id = menu.getId(); 32 | this.name = menu.getName(); 33 | this.menu = menu; 34 | this.count = 1; 35 | } 36 | 37 | public OrderItem(String id, String name, Menu menu, int count) { 38 | this.id = id; 39 | this.name = name; 40 | this.menu = menu; 41 | this.count = count; 42 | } 43 | 44 | public void verify() { 45 | if (!name.equals(menu.getName())) { 46 | throw new IllegalStateException("기존 메뉴가 변경 됨"); 47 | } 48 | } 49 | 50 | public int getMoney() { 51 | return menu.getPrice() * count; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/domain/order/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.domain.order; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | 6 | public interface OrderRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/domain/shop/Menu.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.domain.shop; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Id; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.OneToOne; 12 | import javax.persistence.Table; 13 | 14 | @Getter 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Entity 17 | @Table(name = "MENUS") 18 | public class Menu { 19 | @Id 20 | @Column(name = "MENU_ID") 21 | private String id; 22 | @Column 23 | private String name; 24 | @Column 25 | private int price; 26 | @OneToOne 27 | @JoinColumn(name = "MENU_ID") 28 | private Shop shop; 29 | 30 | public Menu(String id, String name, int price) { 31 | this.id = id; 32 | this.name = name; 33 | this.price = price; 34 | 35 | verifyMenu(); 36 | } 37 | 38 | private void verifyMenu() { 39 | if (id == null || id.isBlank()) { 40 | throw new IllegalStateException("메뉴 id가 없습니다."); 41 | } 42 | 43 | if (name == null || name.isBlank()) { 44 | throw new IllegalStateException("메뉴 이름이 없습니다."); 45 | } 46 | 47 | if (price < 0) { 48 | throw new IllegalStateException("가격은 음수일 수 없습니다."); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/domain/shop/Shop.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.domain.shop; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.Id; 11 | import javax.persistence.JoinColumn; 12 | import javax.persistence.OneToMany; 13 | import javax.persistence.Table; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | @Getter 18 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 19 | @Entity 20 | @Table(name = "SHOPS") 21 | public class Shop { 22 | @Id 23 | @Column(name = "SHOP_ID") 24 | private String id; 25 | @Column 26 | private String name; 27 | @Column 28 | private boolean open; 29 | @Column 30 | private int minPrice; 31 | @OneToMany(cascade = CascadeType.ALL) 32 | @JoinColumn(name = "SHOP_ID") 33 | private List menus = new ArrayList<>(); 34 | 35 | public Shop(String id, String name, int minPrice) { 36 | this.id = id; 37 | this.name = name; 38 | this.minPrice = minPrice; 39 | } 40 | 41 | public void addMenu(Menu menu) { 42 | this.menus.add(menu); 43 | } 44 | 45 | public boolean verifyOrderMoney(int orderMoney) { 46 | return orderMoney >= minPrice; 47 | } 48 | 49 | public void open() { 50 | this.open = true; 51 | } 52 | 53 | public void close() { 54 | this.open = false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/domain/shop/ShopRepository.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.domain.shop; 2 | 3 | 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface ShopRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/service/OrderDto.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.service; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | @Data 9 | public class OrderDto { 10 | private String id; 11 | private String shopId; 12 | private List menuIds = new ArrayList<>(); 13 | } 14 | -------------------------------------------------------------------------------- /spring-tdd/src/main/java/com/zkdlu/tdd/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.service; 2 | 3 | import com.zkdlu.tdd.domain.order.Order; 4 | import com.zkdlu.tdd.domain.order.OrderItem; 5 | import com.zkdlu.tdd.domain.order.OrderRepository; 6 | import com.zkdlu.tdd.domain.shop.Shop; 7 | import com.zkdlu.tdd.domain.shop.ShopRepository; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.stream.Collectors; 13 | 14 | @RequiredArgsConstructor 15 | @Service 16 | public class OrderService { 17 | private final OrderRepository orderRepository; 18 | private final ShopRepository shopRepository; 19 | 20 | @Transactional 21 | public String placeOrder(OrderDto orderDto) { 22 | Shop shop = shopRepository.findById(orderDto.getShopId()) 23 | .orElseThrow(IllegalStateException::new); 24 | 25 | var orderItems = shop.getMenus().stream() 26 | .map(OrderItem::new) 27 | .collect(Collectors.toList()); 28 | 29 | Order order = Order.builder() 30 | .id(orderDto.getId()) 31 | .shop(shop) 32 | .orderItems(orderItems) 33 | .build(); 34 | 35 | order.place(); 36 | orderRepository.save(order); 37 | 38 | return order.getId(); 39 | } 40 | 41 | @Transactional 42 | public String payedOrder(String orderId) { 43 | Order order = orderRepository.findById(orderId) 44 | .orElseThrow(IllegalStateException::new); 45 | 46 | order.payed(); 47 | return order.getId(); 48 | } 49 | 50 | @Transactional 51 | public String acceptOrder(String orderId) { 52 | Order order = orderRepository.findById(orderId) 53 | .orElseThrow(IllegalStateException::new); 54 | 55 | order.accept(); 56 | return order.getId(); 57 | } 58 | 59 | @Transactional 60 | public String deliveryOrder(String orderId) { 61 | Order order = orderRepository.findById(orderId) 62 | .orElseThrow(IllegalStateException::new); 63 | 64 | order.delivery(); 65 | return order.getId(); 66 | } 67 | 68 | @Transactional 69 | public String completeOrder(String orderId) { 70 | Order order = orderRepository.findById(orderId) 71 | .orElseThrow(IllegalStateException::new); 72 | 73 | order.complete(); 74 | return order.getId(); 75 | } 76 | 77 | @Transactional 78 | public String cancelOrder(String orderId) { 79 | Order order = orderRepository.findById(orderId) 80 | .orElseThrow(IllegalStateException::new); 81 | 82 | order.cancel(); 83 | return order.getId(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /spring-tdd/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:h2:tcp://localhost/~/test 4 | driver-class-name: org.h2.Driver 5 | username: sa 6 | password: 7 | jpa: 8 | database-platform: org.hibernate.dialect.H2Dialect 9 | show-sql: true 10 | hibernate: 11 | ddl-auto: validate -------------------------------------------------------------------------------- /spring-tdd/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into shops (shop_id, name, min_price, open) values ('shop1', '건s shop', 10000, true); 2 | insert into menus (menu_id, name, price, shop_id) values ('menu1', '만두', 3000, 'shop1'); 3 | insert into menus (menu_id, name, price, shop_id) values ('menu2', '라면', 4000, 'shop1'); 4 | insert into menus (menu_id, name, price, shop_id) values ('menu3', '고기', 3000, 'shop1'); 5 | insert into menus (menu_id, name, price, shop_id) values ('menu4', '볶음밥', 2000, 'shop1'); 6 | insert into menus (menu_id, name, price, shop_id) values ('menu5', '곱창', 5000, 'shop1'); -------------------------------------------------------------------------------- /spring-tdd/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists menus CASCADE; 2 | drop table if exists orderitems CASCADE; 3 | drop table if exists orders CASCADE; 4 | drop table if exists shops CASCADE; 5 | 6 | create table menus ( 7 | menu_id varchar(255) not null, 8 | name varchar(255), 9 | price integer, 10 | shop_id varchar(255), 11 | primary key (menu_id) 12 | ); 13 | 14 | create table shops ( 15 | shop_id varchar(255) not null, 16 | min_price integer, 17 | name varchar(255), 18 | open boolean, 19 | primary key (shop_id) 20 | ); 21 | 22 | create table orders ( 23 | order_id varchar(255) not null, 24 | order_state varchar(255), 25 | shop_id varchar(255), 26 | primary key (order_id) 27 | ); 28 | 29 | create table orderitems ( 30 | order_item_id varchar(255) not null, 31 | count integer, 32 | order_item_name varchar(255), 33 | menu_id varchar(255), 34 | order_id varchar(255), 35 | primary key (order_item_id) 36 | ); 37 | 38 | alter table menus add constraint FK_menus_shop_id foreign key (shop_id) references shops; 39 | alter table orderitems add constraint FK_orderitems_menu_id foreign key (menu_id) references menus; 40 | alter table orders add constraint FK_orders_shop_id foreign key (shop_id) references shops; -------------------------------------------------------------------------------- /spring-tdd/src/test/java/com/zkdlu/tdd/api/OrderApiTest.java: -------------------------------------------------------------------------------- 1 | package com.zkdlu.tdd.api; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.zkdlu.tdd.service.OrderDto; 5 | import com.zkdlu.tdd.service.OrderService; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 10 | import org.springframework.boot.test.mock.mockito.MockBean; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | import java.util.Arrays; 15 | 16 | import static org.mockito.BDDMockito.given; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @WebMvcTest({OrderApi.class}) 22 | class OrderApiTest { 23 | @Autowired 24 | private MockMvc mockMvc; 25 | 26 | @MockBean 27 | private OrderService orderService; 28 | 29 | @Autowired 30 | private ObjectMapper objectMapper; 31 | 32 | @Test 33 | @DisplayName("주문 요청에 필요한 Parameter가 잘 매핑되었나요?") 34 | void placeOrder() throws Exception { 35 | //given 36 | OrderDto orderDto = new OrderDto(); 37 | orderDto.setId("order-1"); 38 | orderDto.setShopId("shop-1"); 39 | orderDto.setMenuIds(Arrays.asList("menu-1","menu-2","menu-3")); 40 | given(orderService.placeOrder(orderDto)).willReturn("order-1"); 41 | 42 | //when 43 | //then 44 | mockMvc.perform( 45 | post("/") 46 | .content(objectMapper.writeValueAsString(orderDto)) 47 | .contentType(MediaType.APPLICATION_JSON) 48 | ) 49 | .andDo(print()) 50 | .andExpect(status().isOk()); 51 | } 52 | } --------------------------------------------------------------------------------