From 94937b5f7fef8ccd0eeabdb523498c978efc0ad4 Mon Sep 17 00:00:00 2001 From: pdh0128 Date: Thu, 23 Oct 2025 20:09:34 +0900 Subject: [PATCH 1/3] feat :: remain token increase saga --- src/main/avro/MemorialBowedAvroSchema.avsc | 10 +++++ .../avro/RemainTokenIncreaseResponse.avsc | 30 ++++++++++++++ .../domain/controller/UserController.java | 1 - .../eventlistener/KafkaEventListener.java | 40 +++++++++++++++++++ .../domain/service/TokenIncreaseService.java | 29 ++++++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/main/avro/MemorialBowedAvroSchema.avsc create mode 100644 src/main/avro/RemainTokenIncreaseResponse.avsc create mode 100644 src/main/java/com/example/user/domain/service/TokenIncreaseService.java diff --git a/src/main/avro/MemorialBowedAvroSchema.avsc b/src/main/avro/MemorialBowedAvroSchema.avsc new file mode 100644 index 0000000..27bf4fc --- /dev/null +++ b/src/main/avro/MemorialBowedAvroSchema.avsc @@ -0,0 +1,10 @@ +{ + "type": "record", + "name": "MemorialBowedAvroSchema", + "namespace": "windeath44.server.memorial.avro", + "fields": [ + {"name": "memorialId", "type": "long"}, + {"name": "memorialBow", "type": "long"}, + {"name": "writerId", "type": "string"} + ] +} diff --git a/src/main/avro/RemainTokenIncreaseResponse.avsc b/src/main/avro/RemainTokenIncreaseResponse.avsc new file mode 100644 index 0000000..45f7c2b --- /dev/null +++ b/src/main/avro/RemainTokenIncreaseResponse.avsc @@ -0,0 +1,30 @@ +{ + "type": "record", + "name": "RemainTokenIncreaseResponse", + "namespace": "com.example.user.avro", + "doc": "토큰 증가 응답 스키마", + "fields": [ + { + "name": "user_id", + "type": "string", + "doc": "사용자 ID" + }, + { + "name": "success", + "type": "boolean", + "doc": "토큰 증가 성공 여부" + }, + { + "name": "remaining_token", + "type": ["null", "long"], + "default": null, + "doc": "남은 토큰 수 (성공 시)" + }, + { + "name": "error_message", + "type": ["null", "string"], + "default": null, + "doc": "에러 발생 시 에러 메시지" + } + ] +} diff --git a/src/main/java/com/example/user/domain/controller/UserController.java b/src/main/java/com/example/user/domain/controller/UserController.java index 5022e6b..4560b59 100644 --- a/src/main/java/com/example/user/domain/controller/UserController.java +++ b/src/main/java/com/example/user/domain/controller/UserController.java @@ -22,7 +22,6 @@ public class UserController { private final UserService userService; - @GetMapping public ResponseEntity>> getUsersByIds(@RequestParam("userIds") List userIds) { List userResponsesList = userService.findAllByIds(userIds); diff --git a/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java b/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java index cfd35ab..133e945 100644 --- a/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java +++ b/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java @@ -2,19 +2,23 @@ import com.chatbot.events.ChatEvent; import com.example.user.avro.RemainTokenDecreaseResponse; +import com.example.user.avro.RemainTokenIncreaseResponse; import com.example.user.domain.service.TokenDecreaseService; +import com.example.user.domain.service.TokenIncreaseService; import com.example.user.global.infrastructure.KafkaProducer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import windeath44.server.memorial.avro.MemorialBowedAvroSchema; @Slf4j @Component @RequiredArgsConstructor public class KafkaEventListener { private final TokenDecreaseService tokenDecreaseService; + private final TokenIncreaseService tokenIncreaseService; private final KafkaProducer kafkaProducer; @KafkaListener(topics = "remain-token-decrease-request", groupId = "user") @@ -51,5 +55,41 @@ private static RemainTokenDecreaseResponse buildResponse(String userId, boolean .setErrorMessage(errorMessage) .build(); } + + @KafkaListener(topics = "remain-token-increase-request", groupId = "user") + @Transactional + public void handleTokenIncreaseRequest(MemorialBowedAvroSchema memorialBowedAvroSchema) { + String userId = memorialBowedAvroSchema.getWriterId(); + log.info("토큰 증가 요청 수신 - userId: {}, tokenBowCount: {}", userId, memorialBowedAvroSchema.getMemorialBow()); + + try { + Long remainingToken = tokenIncreaseService.increaseToken( + userId, + 1000L + ); + + RemainTokenIncreaseResponse response = buildIncreaseResponse(userId, true, remainingToken, null); + kafkaProducer.send("remain-token-increase-response", response); + + log.info("토큰 증가 성공 - userId: {}, remainingToken: {}", userId, remainingToken); + + } catch (Exception e) { + log.error("토큰 증가 실패 - userId: {}, error: {}", userId, e.getMessage(), e); + + RemainTokenIncreaseResponse response = buildIncreaseResponse(userId, false, null, e.getMessage()); + kafkaProducer.send("remain-token-increase-fail-response", response); + + log.info("토큰 증가 실패 응답 발송 완료 - userId: {}", userId); + } + } + + private static RemainTokenIncreaseResponse buildIncreaseResponse(String userId, boolean success, Long remainingToken, String errorMessage) { + return RemainTokenIncreaseResponse.newBuilder() + .setUserId(userId) + .setSuccess(success) + .setRemainingToken(remainingToken) + .setErrorMessage(errorMessage) + .build(); + } } diff --git a/src/main/java/com/example/user/domain/service/TokenIncreaseService.java b/src/main/java/com/example/user/domain/service/TokenIncreaseService.java new file mode 100644 index 0000000..657f4e6 --- /dev/null +++ b/src/main/java/com/example/user/domain/service/TokenIncreaseService.java @@ -0,0 +1,29 @@ +package com.example.user.domain.service; + +import com.example.user.domain.exception.NotFoundUserException; +import com.example.user.domain.model.User; +import com.example.user.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class TokenIncreaseService { + private final UserRepository userRepository; + + @Transactional + public Long increaseToken(String userId, Long tokenCount) { + User user = userRepository.findByUserId(userId) + .orElseThrow(NotFoundUserException::getInstance); + + user.increaseToken(tokenCount); + userRepository.save(user); + + log.info("토큰 증가 완료 - userId: {}, 증가량: {}, 남은 토큰: {}", userId, tokenCount, user.getRemainToken()); + + return user.getRemainToken(); + } +} From 7c81eb7b2c46dc4dd207cc78be4216197b46817a Mon Sep 17 00:00:00 2001 From: pdh0128 Date: Fri, 24 Oct 2025 00:23:01 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore=20::=20avro=20schema=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/avro/{chat.avsc => ChatAvroSchema.avsc} | 4 ++-- src/main/avro/RemainTokenDecreaseResponse.avsc | 2 +- src/main/avro/RemainTokenIncreaseResponse.avsc | 2 +- .../user/domain/eventlistener/KafkaEventListener.java | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/main/avro/{chat.avsc => ChatAvroSchema.avsc} (95%) diff --git a/src/main/avro/chat.avsc b/src/main/avro/ChatAvroSchema.avsc similarity index 95% rename from src/main/avro/chat.avsc rename to src/main/avro/ChatAvroSchema.avsc index f9561e3..fe5f46d 100644 --- a/src/main/avro/chat.avsc +++ b/src/main/avro/ChatAvroSchema.avsc @@ -1,7 +1,7 @@ { "type": "record", - "name": "ChatEvent", - "namespace": "com.chatbot.events", + "name": "ChatAvroSchema", + "namespace": "windeath44.server.chatbot.avro", "doc": "챗봇 채팅 이벤트 스키마", "fields": [ { diff --git a/src/main/avro/RemainTokenDecreaseResponse.avsc b/src/main/avro/RemainTokenDecreaseResponse.avsc index 5d2843e..351cf22 100644 --- a/src/main/avro/RemainTokenDecreaseResponse.avsc +++ b/src/main/avro/RemainTokenDecreaseResponse.avsc @@ -1,7 +1,7 @@ { "type": "record", "name": "RemainTokenDecreaseResponse", - "namespace": "com.example.user.avro", + "namespace": "windeath44.server.user.avro", "doc": "토큰 감소 응답 스키마", "fields": [ { diff --git a/src/main/avro/RemainTokenIncreaseResponse.avsc b/src/main/avro/RemainTokenIncreaseResponse.avsc index 45f7c2b..536412e 100644 --- a/src/main/avro/RemainTokenIncreaseResponse.avsc +++ b/src/main/avro/RemainTokenIncreaseResponse.avsc @@ -1,7 +1,7 @@ { "type": "record", "name": "RemainTokenIncreaseResponse", - "namespace": "com.example.user.avro", + "namespace": "windeath44.server.user.avro", "doc": "토큰 증가 응답 스키마", "fields": [ { diff --git a/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java b/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java index 133e945..e765fc9 100644 --- a/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java +++ b/src/main/java/com/example/user/domain/eventlistener/KafkaEventListener.java @@ -1,8 +1,6 @@ package com.example.user.domain.eventlistener; -import com.chatbot.events.ChatEvent; -import com.example.user.avro.RemainTokenDecreaseResponse; -import com.example.user.avro.RemainTokenIncreaseResponse; +import windeath44.server.chatbot.avro.ChatAvroSchema; import com.example.user.domain.service.TokenDecreaseService; import com.example.user.domain.service.TokenIncreaseService; import com.example.user.global.infrastructure.KafkaProducer; @@ -12,6 +10,8 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import windeath44.server.memorial.avro.MemorialBowedAvroSchema; +import windeath44.server.user.avro.RemainTokenDecreaseResponse; +import windeath44.server.user.avro.RemainTokenIncreaseResponse; @Slf4j @Component @@ -23,7 +23,7 @@ public class KafkaEventListener { @KafkaListener(topics = "remain-token-decrease-request", groupId = "user") @Transactional - public void handleTokenDecreaseRequest(ChatEvent request) { + public void handleTokenDecreaseRequest(ChatAvroSchema request) { log.info("토큰 감소 요청 수신 - userId: {}, tokenCount: {}", request.getUserId(), request.getTotalTokenCount()); try { From a65773570cba0df21608d5a77b4cc4b185e0f3eb Mon Sep 17 00:00:00 2001 From: pdh0128 Date: Sat, 25 Oct 2025 23:56:40 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat=20::=20remian=20token=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=203000=EA=B0=9C=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/user/domain/model/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/user/domain/model/User.java b/src/main/java/com/example/user/domain/model/User.java index bf98d4c..e4bc58d 100644 --- a/src/main/java/com/example/user/domain/model/User.java +++ b/src/main/java/com/example/user/domain/model/User.java @@ -34,7 +34,7 @@ public class User { @PrePersist public void defaultSettings() { - this.remainToken = 0L; + this.remainToken = 3000L; String defaultImage = "https://windeath44.s3.ap-northeast-2.amazonaws.com/seori_profile.png"; this.profile = defaultImage; }