diff --git a/src/main/java/com/dailyon/auctionservice/AuctionServiceApplication.java b/src/main/java/com/dailyon/auctionservice/AuctionServiceApplication.java index ffd34c6..0377bd0 100644 --- a/src/main/java/com/dailyon/auctionservice/AuctionServiceApplication.java +++ b/src/main/java/com/dailyon/auctionservice/AuctionServiceApplication.java @@ -41,11 +41,6 @@ public void setTimezoneToSeoul() { @PostConstruct @Profile({"!test"}) public void setDynamoDB() { -// TableUtils.deleteTableIfExists( -// dynamoDB, dynamoDBMapper.generateDeleteTableRequest(Auction.class)); - -// TableUtils.deleteTableIfExists( -// dynamoDB, dynamoDBMapper.generateDeleteTableRequest(BidHistory.class)); TableUtils.deleteTableIfExists( dynamoDB, dynamoDBMapper.generateDeleteTableRequest(AuctionHistory.class)); @@ -58,28 +53,40 @@ public void setDynamoDB() { CreateTableRequest createBidHistory = dynamoDBMapper .generateCreateTableRequest(BidHistory.class) - .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)); + .withProvisionedThroughput(new ProvisionedThroughput(1000L, 1000L)); - CreateTableRequest createAuctionHistory = dynamoDBMapper + CreateTableRequest createAuctionHistory = + dynamoDBMapper .generateCreateTableRequest(AuctionHistory.class) .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)); createBidHistory .getGlobalSecondaryIndexes() - .forEach(idx -> idx - .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)) - .withProjection(new Projection().withProjectionType("ALL")) - ); - + .forEach( + idx -> + idx.withProvisionedThroughput(new ProvisionedThroughput(1000L, 1000L)) + .withProjection(new Projection().withProjectionType("ALL"))); createAuctionHistory - .getGlobalSecondaryIndexes() - .forEach(idx -> idx - .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)) - .withProjection(new Projection().withProjectionType("ALL")) - ); - + .getGlobalSecondaryIndexes() + .forEach( + idx -> + idx.withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)) + .withProjection(new Projection().withProjectionType("ALL"))); TableUtils.createTableIfNotExists(dynamoDB, createAuction); TableUtils.createTableIfNotExists(dynamoDB, createBidHistory); TableUtils.createTableIfNotExists(dynamoDB, createAuctionHistory); } + + @PreDestroy + @Profile({"!test"}) + public void deleteDB() { + TableUtils.deleteTableIfExists( + dynamoDB, dynamoDBMapper.generateDeleteTableRequest(Auction.class)); + + TableUtils.deleteTableIfExists( + dynamoDB, dynamoDBMapper.generateDeleteTableRequest(BidHistory.class)); + + TableUtils.deleteTableIfExists( + dynamoDB, dynamoDBMapper.generateDeleteTableRequest(AuctionHistory.class)); + } } diff --git a/src/main/java/com/dailyon/auctionservice/chat/messaging/RedisChatMessagePublisher.java b/src/main/java/com/dailyon/auctionservice/chat/messaging/RedisChatMessagePublisher.java index 462e518..b0b91d5 100644 --- a/src/main/java/com/dailyon/auctionservice/chat/messaging/RedisChatMessagePublisher.java +++ b/src/main/java/com/dailyon/auctionservice/chat/messaging/RedisChatMessagePublisher.java @@ -44,7 +44,6 @@ public Mono publishChatMessage(String message) { String result = "EMPTY_MESSAGE"; try { ChatPayload chatPayload = objectMapper.readValue(message, ChatPayload.class); - log.info("chat payload -> {}", chatPayload); result = objectMapper.writeValueAsString(chatPayload); } catch (JsonProcessingException e) { log.error("Error converting ChatMessage {} into string", message, e); diff --git a/src/main/java/com/dailyon/auctionservice/config/LocalRedisConfig.java b/src/main/java/com/dailyon/auctionservice/config/LocalRedisConfig.java index b99d595..f98d476 100644 --- a/src/main/java/com/dailyon/auctionservice/config/LocalRedisConfig.java +++ b/src/main/java/com/dailyon/auctionservice/config/LocalRedisConfig.java @@ -2,6 +2,7 @@ import com.dailyon.auctionservice.chat.messaging.RedisChatMessageListener; import com.dailyon.auctionservice.document.BidHistory; +import com.dailyon.auctionservice.dto.response.BidInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -64,18 +65,18 @@ ReactiveStringRedisTemplate reactiveStringRedisTemplate( } @Bean("reactiveRedisTemplateForBid") - public ReactiveRedisTemplate reactiveRedisTemplate( + public ReactiveRedisTemplate reactiveRedisTemplate( ReactiveRedisConnectionFactory factory) { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); // Java 8 날짜/시간 모듈 등록 objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 날짜를 timestamp가 아닌 ISO 형식으로 출력 - Jackson2JsonRedisSerializer serializer = - new Jackson2JsonRedisSerializer<>(BidHistory.class); + Jackson2JsonRedisSerializer serializer = + new Jackson2JsonRedisSerializer<>(BidInfo.class); serializer.setObjectMapper(objectMapper); - RedisSerializationContext serializationContext = - RedisSerializationContext.newSerializationContext( + RedisSerializationContext serializationContext = + RedisSerializationContext.newSerializationContext( new StringRedisSerializer()) .value(serializer) .build(); diff --git a/src/main/java/com/dailyon/auctionservice/controller/BidAdminController.java b/src/main/java/com/dailyon/auctionservice/controller/BidAdminController.java index 3a6fcf3..7e2e1d2 100644 --- a/src/main/java/com/dailyon/auctionservice/controller/BidAdminController.java +++ b/src/main/java/com/dailyon/auctionservice/controller/BidAdminController.java @@ -15,7 +15,6 @@ public class BidAdminController { @GetMapping("/bids/start") public Mono start(@RequestHeader(name = "role") String role) { - bidFacade.start(); - return Mono.empty(); + return bidFacade.start(); } } diff --git a/src/main/java/com/dailyon/auctionservice/controller/BidApiController.java b/src/main/java/com/dailyon/auctionservice/controller/BidApiController.java index 3b0c933..4b7f12b 100644 --- a/src/main/java/com/dailyon/auctionservice/controller/BidApiController.java +++ b/src/main/java/com/dailyon/auctionservice/controller/BidApiController.java @@ -19,7 +19,6 @@ public class BidApiController { @PostMapping("") public Mono bidding( @RequestHeader("memberId") String memberId, @RequestBody CreateBidRequest request) { - log.info("memberId {} ", memberId); return bidFacade.createBid(request, memberId); } } diff --git a/src/main/java/com/dailyon/auctionservice/controller/ChatHandler.java b/src/main/java/com/dailyon/auctionservice/controller/ChatHandler.java index f09860b..a6e3897 100644 --- a/src/main/java/com/dailyon/auctionservice/controller/ChatHandler.java +++ b/src/main/java/com/dailyon/auctionservice/controller/ChatHandler.java @@ -97,7 +97,6 @@ public Mono sendMessage(ChatPayload chatMessage) { } public Mono biddingBroadCast(ChatPayload chatPayload) { - log.info("payload {}", chatPayload); return objectStringConverter .objectToString(chatPayload) .flatMap(redisChatMessagePublisher::publishChatMessage) diff --git a/src/main/java/com/dailyon/auctionservice/dto/response/BidInfo.java b/src/main/java/com/dailyon/auctionservice/dto/response/BidInfo.java new file mode 100644 index 0000000..04f7d02 --- /dev/null +++ b/src/main/java/com/dailyon/auctionservice/dto/response/BidInfo.java @@ -0,0 +1,25 @@ +package com.dailyon.auctionservice.dto.response; + +import com.dailyon.auctionservice.document.BidHistory; +import lombok.*; + +@ToString +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BidInfo { + private String memberId; + private String nickname; + private String auctionId; + private String round; + + public static BidInfo from(BidHistory history) { + return BidInfo.builder() + .memberId(history.getMemberId()) + .nickname(history.getNickname()) + .auctionId(history.getAuctionId()) + .round(history.getRound()) + .build(); + } +} diff --git a/src/main/java/com/dailyon/auctionservice/dto/response/TopBidderResponse.java b/src/main/java/com/dailyon/auctionservice/dto/response/TopBidderResponse.java index 0c67b43..028d3f3 100644 --- a/src/main/java/com/dailyon/auctionservice/dto/response/TopBidderResponse.java +++ b/src/main/java/com/dailyon/auctionservice/dto/response/TopBidderResponse.java @@ -1,6 +1,5 @@ package com.dailyon.auctionservice.dto.response; -import com.dailyon.auctionservice.document.BidHistory; import lombok.Builder; import lombok.Getter; @@ -11,12 +10,11 @@ public class TopBidderResponse { private String nickname; private Long bidAmount; - public static TopBidderResponse from(BidHistory bidHistory) { + public static TopBidderResponse from(BidInfo bidInfo, Long bidAmount) { return TopBidderResponse.builder() - .memberId(bidHistory.getMemberId()) - .nickname(bidHistory.getNickname()) - .bidAmount(bidHistory.getBidAmount()) + .memberId(bidInfo.getMemberId()) + .nickname(bidInfo.getNickname()) + .bidAmount(bidAmount) .build(); } - ; } diff --git a/src/main/java/com/dailyon/auctionservice/facade/BidFacade.java b/src/main/java/com/dailyon/auctionservice/facade/BidFacade.java index e38de1e..373d908 100644 --- a/src/main/java/com/dailyon/auctionservice/facade/BidFacade.java +++ b/src/main/java/com/dailyon/auctionservice/facade/BidFacade.java @@ -29,7 +29,6 @@ public Mono createBid(CreateBidRequest request, String memberId) { Mono bidAmountMono = bidService.create(request, memberId); Mono> topBidderMono = bidService.getTopBidder(request, maximumWinner); - return bidAmountMono.flatMap( bidAmount -> topBidderMono.flatMap( @@ -40,8 +39,8 @@ public Mono createBid(CreateBidRequest request, String memberId) { })); } - public void start() { + public Mono start() { ChatPayload payload = ChatPayload.of(ChatCommand.START, null); - chatHandler.sendStart(payload); + return chatHandler.sendStart(payload); } } diff --git a/src/main/java/com/dailyon/auctionservice/repository/ReactiveRedisRepository.java b/src/main/java/com/dailyon/auctionservice/repository/ReactiveRedisRepository.java index d2ca19b..5fa3aa6 100644 --- a/src/main/java/com/dailyon/auctionservice/repository/ReactiveRedisRepository.java +++ b/src/main/java/com/dailyon/auctionservice/repository/ReactiveRedisRepository.java @@ -1,7 +1,11 @@ package com.dailyon.auctionservice.repository; +import com.dailyon.auctionservice.document.Auction; import com.dailyon.auctionservice.document.BidHistory; import com.dailyon.auctionservice.dto.request.CreateBidRequest; +import com.dailyon.auctionservice.dto.response.BidInfo; +import com.dailyon.auctionservice.dto.response.TopBidderResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.Range; @@ -13,36 +17,63 @@ import java.time.Duration; +@Slf4j @Profile("!test") @Repository public class ReactiveRedisRepository { private static final String AUCTION_KEY = "auction_id:"; - private static final String ROUND_KEY = ":round:"; - private final ReactiveZSetOperations reactiveRedisZSet; - private final ReactiveRedisTemplate reactiveRedisTemplate; + private static final String MEMBER_ID = ":member_id:"; + private final ReactiveZSetOperations reactiveRedisZSet; + private final ReactiveRedisTemplate reactiveRedisTemplate; public ReactiveRedisRepository( @Qualifier("reactiveRedisTemplateForBid") - ReactiveRedisTemplate reactiveRedisTemplate) { + ReactiveRedisTemplate reactiveRedisTemplate) { this.reactiveRedisZSet = reactiveRedisTemplate.opsForZSet(); this.reactiveRedisTemplate = reactiveRedisTemplate; } - public Mono save(BidHistory history) { - String key = generateKey(history.getAuctionId(), history.getRound()); + public Mono save(BidHistory history, Auction auction) { + String key = generateKey(history.getAuctionId()); + BidInfo bidInfo = BidInfo.from(history); + long lowerBound = 0L; + long upperBound = auction.getMaximumWinner() - 1; return reactiveRedisZSet - .add(key, history, history.getBidAmount()) - .flatMap(success -> reactiveRedisTemplate.expire(key, Duration.ofHours(1L))) - .then(); + .rank(key, bidInfo) + .flatMap( + rank -> { + if (rank >= lowerBound && rank <= upperBound) { + // value가 원하는 범위 내에 있으므로, score(bidAmount)에 auction.getAskingPrice() 값을 더한다. + return reactiveRedisZSet.incrementScore(key, bidInfo, auction.getAskingPrice()); + } else { + return Mono.empty(); + } + }) + .switchIfEmpty( + reactiveRedisZSet + .add(key, bidInfo, history.getBidAmount()) + .thenReturn(history.getBidAmount().doubleValue())) + .flatMap( + bidAmount -> { + reactiveRedisTemplate.expire(key, Duration.ofHours(1L)); + return Mono.just(bidAmount); + }); } - public Flux getTopBidder(CreateBidRequest request, int maximum) { - String key = generateKey(request.getAuctionId(), request.getRound()); - return reactiveRedisZSet.reverseRange( - key, Range.from(Range.Bound.inclusive(0L)).to(Range.Bound.inclusive((long) maximum-1))); + public Flux getTopBidder(CreateBidRequest request, int maximum) { + String key = generateKey(request.getAuctionId()); + return reactiveRedisZSet + .reverseRange( + key, + Range.from(Range.Bound.inclusive(0L)).to(Range.Bound.inclusive((long) maximum - 1))) + .flatMap( + bidInfo -> + reactiveRedisZSet + .score(key, bidInfo) + .map(score -> TopBidderResponse.from(bidInfo, Math.round(score)))); } - private String generateKey(String auctionId, String round) { - return AUCTION_KEY + auctionId + ROUND_KEY + round; + private String generateKey(String auctionId) { + return AUCTION_KEY + auctionId; } } diff --git a/src/main/java/com/dailyon/auctionservice/service/BidService.java b/src/main/java/com/dailyon/auctionservice/service/BidService.java index 460c68e..1a26d84 100644 --- a/src/main/java/com/dailyon/auctionservice/service/BidService.java +++ b/src/main/java/com/dailyon/auctionservice/service/BidService.java @@ -21,16 +21,23 @@ public class BidService { public Mono create(CreateBidRequest request, String memberId) { BidHistory bidHistory = request.toEntity(memberId); - return reactiveRedisRepository - .save(bidHistory) - .then(Mono.just(bidHistoryRepository.save(bidHistory))) - .map(BidHistory::getBidAmount); + + return Mono.justOrEmpty( + auctionRepository + .findById(request.getAuctionId()) + .orElseThrow(() -> new RuntimeException("해당 경매정보가 존재하지 않습니다."))) + .flatMap( + auction -> + reactiveRedisRepository + .save(bidHistory, auction) + .flatMap( + bidAmount -> { + bidHistoryRepository.save(bidHistory); + return Mono.just(bidAmount.longValue()); + })); } public Mono> getTopBidder(CreateBidRequest request, int maximumWinner) { - return reactiveRedisRepository - .getTopBidder(request, maximumWinner) - .map(TopBidderResponse::from) - .collectList(); + return reactiveRedisRepository.getTopBidder(request, maximumWinner).collectList(); } }