Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 쿠폰, 알림 테스트 접근 제어자, 메서드명, 클래스명 변경 #148

Merged
merged 17 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public PurchaseProductResponse purchaseBugProduct(Long memberId, Long productId,
Payment payment = PaymentMapper.toPayment(memberId, product);

if (!isNull(request.couponWalletId())) {
Coupon coupon = couponService.getByWallet(request.couponWalletId(), memberId);
Coupon coupon = couponService.getByWalletIdAndMemberId(request.couponWalletId(), memberId);
payment.applyCoupon(coupon, request.couponWalletId());
}
paymentRepository.save(payment);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.moabam.api.application.coupon;

import java.time.LocalDate;
import java.util.Optional;
import java.util.Set;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import com.moabam.api.domain.coupon.Coupon;
import com.moabam.api.domain.coupon.CouponWallet;
import com.moabam.api.domain.coupon.repository.CouponManageRepository;
import com.moabam.api.domain.coupon.repository.CouponRepository;
import com.moabam.api.domain.coupon.repository.CouponWalletRepository;
import com.moabam.global.auth.model.AuthMember;
import com.moabam.global.common.util.ClockHolder;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.model.ErrorMessage;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class CouponManageService {

private static final long ISSUE_SIZE = 10;

private final ClockHolder clockHolder;

private final CouponRepository couponRepository;
private final CouponManageRepository couponManageRepository;
private final CouponWalletRepository couponWalletRepository;

@Scheduled(fixedDelay = 1000)
public void issue() {
LocalDate now = LocalDate.from(clockHolder.times());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C: 늦게 pr 올려서 죄송함다! ClockHolder에 date() 생겨서 그거 쓰시면 편할 것 같아요!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오케이바리요!

Optional<Coupon> isCoupon = couponRepository.findByStartAt(now);

if (!canIssue(isCoupon)) {
return;
}

Coupon coupon = isCoupon.get();
Set<Long> membersId = couponManageRepository.popMinQueue(coupon.getName(), ISSUE_SIZE);

membersId.forEach(memberId -> {
int nextStock = couponManageRepository.increaseIssuedStock(coupon.getName());

if (coupon.getStock() < nextStock) {
return;
}

CouponWallet couponWallet = CouponWallet.create(memberId, coupon);
couponWalletRepository.save(couponWallet);
});
}

public void register(AuthMember authMember, String couponName) {
double registerTime = System.currentTimeMillis();
validateRegister(couponName);
couponManageRepository.addIfAbsentQueue(couponName, authMember.id(), registerTime);
}

public void deleteCouponManage(String couponName) {
couponManageRepository.deleteQueue(couponName);
couponManageRepository.deleteIssuedStock(couponName);
}

private void validateRegister(String couponName) {
LocalDate now = LocalDate.from(clockHolder.times());
Optional<Coupon> coupon = couponRepository.findByStartAt(now);

if (coupon.isEmpty() || !coupon.get().getName().equals(couponName)) {
throw new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD);
}
}

private boolean canIssue(Optional<Coupon> coupon) {
if (coupon.isEmpty()) {
return false;
}

Coupon currentCoupon = coupon.get();
int currentStock = couponManageRepository.getIssuedStock(currentCoupon.getName());
int maxStock = currentCoupon.getStock();

return currentStock < maxStock;
}
}

This file was deleted.

26 changes: 12 additions & 14 deletions src/main/java/com/moabam/api/application/coupon/CouponService.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@
@Transactional(readOnly = true)
public class CouponService {

private final ClockHolder clockHolder;
private final CouponManageService couponManageService;

private final CouponRepository couponRepository;
private final CouponSearchRepository couponSearchRepository;
private final CouponWalletSearchRepository couponWalletSearchRepository;
private final ClockHolder clockHolder;

@Transactional
public void create(AuthMember admin, CreateCouponRequest request) {
validateAdminRole(admin);
validateConflictName(request.name());
validateConflictStartAt(request.startAt());
validatePeriod(request.startAt(), request.openAt());

Coupon coupon = CouponMapper.toEntity(admin.id(), request);
Expand All @@ -49,6 +52,7 @@ public void delete(AuthMember admin, Long couponId) {
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON));
couponRepository.delete(coupon);
couponManageService.deleteCouponManage(coupon.getName());
}

public CouponResponse getById(Long couponId) {
Expand All @@ -58,7 +62,7 @@ public CouponResponse getById(Long couponId) {
return CouponMapper.toDto(coupon);
}

public Coupon getByWallet(Long couponWalletId, Long memberId) {
public Coupon getByWalletIdAndMemberId(Long couponWalletId, Long memberId) {
return couponWalletSearchRepository.findByIdAndMemberId(couponWalletId, memberId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET))
.getCoupon();
Expand All @@ -73,18 +77,6 @@ public List<CouponResponse> getAllByStatus(CouponStatusRequest request) {
.toList();
}

public Coupon validatePeriod(String couponName) {
LocalDate now = LocalDate.from(clockHolder.times());
Coupon coupon = couponRepository.findByName(couponName)
.orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON));

if (!now.equals(coupon.getStartAt())) {
throw new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD);
}

return coupon;
}

private void validatePeriod(LocalDate startAt, LocalDate openAt) {
LocalDate now = LocalDate.from(clockHolder.times());

Expand All @@ -108,4 +100,10 @@ private void validateConflictName(String couponName) {
throw new ConflictException(ErrorMessage.CONFLICT_COUPON_NAME);
}
}

private void validateConflictStartAt(LocalDate startAt) {
if (couponRepository.existsByStartAt(startAt)) {
throw new ConflictException(ErrorMessage.CONFLICT_COUPON_START_AT);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class NotificationService {

private static final String KNOCK_BODY = "%s님이 콕 찔렀습니다.";
private static final String CERTIFY_TIME_BODY = "%s방 인증 시간입니다.";
private static final String KNOCK_KEY = "room_%s_member_%s_knocks_%s";

private final FcmService fcmService;
private final RoomService roomService;
Expand All @@ -41,13 +40,11 @@ public class NotificationService {
@Transactional
public void sendKnock(AuthMember member, Long targetId, Long roomId) {
roomService.validateRoomById(roomId);

String knockKey = generateKnockKey(member.id(), targetId, roomId);
validateConflictKnock(knockKey);
validateConflictKnock(member.id(), targetId, roomId);

String fcmToken = fcmService.findTokenByMemberId(targetId);
fcmService.sendAsync(fcmToken, String.format(KNOCK_BODY, member.nickname()));
notificationRepository.saveKnock(knockKey);
notificationRepository.saveKnock(member.id(), targetId, roomId);
}

@Scheduled(cron = "0 50 * * * *")
Expand All @@ -68,8 +65,8 @@ public List<Long> getMyKnockStatusInRoom(Long memberId, Long roomId, List<Partic
.filter(participant -> !participant.getMemberId().equals(memberId))
.toList();

Predicate<Long> knockPredicate = targetId
-> notificationRepository.existsKnockByKey(generateKnockKey(memberId, targetId, roomId));
Predicate<Long> knockPredicate = targetId ->
notificationRepository.existsKnockByKey(memberId, targetId, roomId);

Map<Boolean, List<Long>> knockStatus = filteredParticipants.stream()
.map(Participant::getMemberId)
Expand All @@ -78,13 +75,9 @@ public List<Long> getMyKnockStatusInRoom(Long memberId, Long roomId, List<Partic
return knockStatus.get(true);
}

private void validateConflictKnock(String knockKey) {
if (notificationRepository.existsKnockByKey(knockKey)) {
private void validateConflictKnock(Long memberId, Long targetId, Long roomId) {
if (notificationRepository.existsKnockByKey(memberId, targetId, roomId)) {
throw new ConflictException(ErrorMessage.CONFLICT_KNOCK);
}
}

private String generateKnockKey(Long memberId, Long targetId, Long roomId) {
return String.format(KNOCK_KEY, roomId, memberId, targetId);
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/moabam/api/domain/coupon/Coupon.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class Coupon extends BaseTimeEntity {
@Column(name = "stock", nullable = false)
private int stock;

@Column(name = "start_at", nullable = false)
@Column(name = "start_at", unique = true, nullable = false)
private LocalDate startAt;

@Column(name = "open_at", nullable = false)
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/com/moabam/api/domain/coupon/CouponWallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -34,9 +33,12 @@ public class CouponWallet extends BaseTimeEntity {
@ManyToOne(fetch = FetchType.LAZY)
private Coupon coupon;

@Builder
private CouponWallet(Long memberId, Coupon coupon) {
this.memberId = memberId;
this.coupon = coupon;
}

public static CouponWallet create(Long memberId, Coupon coupon) {
return new CouponWallet(memberId, coupon);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.moabam.api.domain.coupon.repository;

import static java.util.Objects.*;

import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Repository;

import com.moabam.api.infrastructure.redis.ValueRedisRepository;
import com.moabam.api.infrastructure.redis.ZSetRedisRepository;

import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class CouponManageRepository {

private static final String STOCK_KEY = "%s_INCR";

private final ZSetRedisRepository zSetRedisRepository;
private final ValueRedisRepository valueRedisRepository;

public void addIfAbsentQueue(String couponName, Long memberId, double registerTime) {
zSetRedisRepository.addIfAbsent(requireNonNull(couponName), requireNonNull(memberId), registerTime);
}

public Set<Long> popMinQueue(String couponName, long count) {
return zSetRedisRepository
.popMin(requireNonNull(couponName), count)
.stream()
.map(tuple -> (Long)tuple.getValue())
.collect(Collectors.toSet());
}

public void deleteQueue(String couponName) {
valueRedisRepository.delete(requireNonNull(couponName));
}

public int increaseIssuedStock(String couponName) {
String stockKey = String.format(STOCK_KEY, requireNonNull(couponName));

return valueRedisRepository
.increment(requireNonNull(stockKey))
.intValue();
}

public int getIssuedStock(String couponName) {
String stockKey = String.format(STOCK_KEY, requireNonNull(couponName));
String stockValue = valueRedisRepository.get(requireNonNull(stockKey));

if (stockValue == null) {
return 0;
}

return Integer.parseInt(stockValue);
}

public void deleteIssuedStock(String couponName) {
String stockKey = String.format(STOCK_KEY, requireNonNull(couponName));
valueRedisRepository.delete(requireNonNull(stockKey));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.moabam.api.domain.coupon.repository;

import java.time.LocalDate;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -8,7 +9,9 @@

public interface CouponRepository extends JpaRepository<Coupon, Long> {

Optional<Coupon> findByName(String couponName);

boolean existsByName(String name);

boolean existsByStartAt(LocalDate startAt);

Optional<Coupon> findByStartAt(LocalDate startAt);
}
Loading