diff --git a/src/main/kotlin/ru/quipy/apigateway/APIController.kt b/src/main/kotlin/ru/quipy/apigateway/APIController.kt index 6f23fa18d..c7727f43d 100644 --- a/src/main/kotlin/ru/quipy/apigateway/APIController.kt +++ b/src/main/kotlin/ru/quipy/apigateway/APIController.kt @@ -4,12 +4,13 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.* +import ru.quipy.common.utils.RateLimiter import ru.quipy.orders.repository.OrderRepository import ru.quipy.payments.logic.OrderPayer import java.util.* @RestController -class APIController { +class APIController(val rateLimiter: RateLimiter) { val logger: Logger = LoggerFactory.getLogger(APIController::class.java) @@ -56,19 +57,24 @@ class APIController { @PostMapping("/orders/{orderId}/payment") fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { - val paymentId = UUID.randomUUID() - val order = orderRepository.findById(orderId)?.let { - orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) - it - } ?: throw IllegalArgumentException("No such order $orderId") + if (!rateLimiter.tick()) { + throw TooManyRequestsException("Payment rate limit exceeded. Please try again later.") + } + val paymentId = UUID.randomUUID() + val order = orderRepository.findById(orderId)?.let { + orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) + it + } ?: throw IllegalArgumentException("No such order $orderId") - val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) - return PaymentSubmissionDto(createdAt, paymentId) - } + val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) + return PaymentSubmissionDto(createdAt, paymentId) + } class PaymentSubmissionDto( val timestamp: Long, val transactionId: UUID ) -} \ No newline at end of file +} + +class TooManyRequestsException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/config/RateLimiterConfig.kt b/src/main/kotlin/ru/quipy/config/RateLimiterConfig.kt new file mode 100644 index 000000000..92a88eed8 --- /dev/null +++ b/src/main/kotlin/ru/quipy/config/RateLimiterConfig.kt @@ -0,0 +1,19 @@ +package ru.quipy.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import ru.quipy.common.utils.LeakingBucketRateLimiter +import java.time.Duration + +@Configuration +class RateLimiterConfig { + + @Bean + fun paymentRateLimiter(): LeakingBucketRateLimiter { + return LeakingBucketRateLimiter( + rate = 9, + window = Duration.ofSeconds(1), + bucketSize = 11 + ) + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33d51a58b..68d31bae5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,17 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} -payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file +# payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +payment.accounts=${PAYMENT_ACCOUNTS:acc-3} +payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} + +# ==================== ??????? ??????????? ==================== +logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx +logging.level.ru.quipy=DEBUG +logging.level.org.springframework=INFO +logging.file.name=logs/application.log +logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n +spring.output.ansi.enabled=ALWAYS + +logging.level.com.zaxxer.hikari=WARN +logging.level.org.hibernate=INFO \ No newline at end of file diff --git a/test-local-run.http b/test-local-run.http index dc8dbeedf..9b76fed12 100644 --- a/test-local-run.http +++ b/test-local-run.http @@ -5,8 +5,8 @@ Content-Type: application/json { "serviceName": "{{serviceName}}", "token": "{{token}}", - "ratePerSecond": 1, - "testCount": 100, + "ratePerSecond": 11, + "testCount": 1200, "processingTimeMillis": 80000 } diff --git a/test-on-prem-run.http b/test-on-prem-run.http index 8233cbe0d..a7f7344db 100644 --- a/test-on-prem-run.http +++ b/test-on-prem-run.http @@ -6,7 +6,7 @@ Content-Type: application/json { "serviceName": "{{serviceName}}", "token": "{{token}}", - "branch": "main", + "branch": "feature/highload-leaky-bucket-case1", "accounts": "acc-3", "ratePerSecond": 11, "testCount": 1200,