Skip to content

Commit

Permalink
hotfix: 쿠폰 발급이 안되는 버그 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
hongdosan committed Nov 30, 2023
1 parent 109e04c commit 0fd11b3
Show file tree
Hide file tree
Showing 21 changed files with 125 additions and 116 deletions.
2 changes: 1 addition & 1 deletion infra/mysql/initdb.d/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ create table coupon
point integer default 1 not null,
description varchar(50) default '',
type enum ('DISCOUNT','GOLDEN','MORNING','NIGHT') not null,
stock integer default 1 not null,
max_count integer default 1 not null,
start_at date not null unique,
open_at date not null,
admin_id bigint not null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.moabam.api.domain.coupon.repository.CouponWalletRepository;
import com.moabam.global.common.util.ClockHolder;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.exception.ConflictException;
import com.moabam.global.error.model.ErrorMessage;

import lombok.RequiredArgsConstructor;
Expand All @@ -28,7 +29,6 @@ public class CouponManageService {
private static final String SUCCESS_ISSUE_BODY = "%s 쿠폰 발행을 성공했습니다. 축하드립니다!";
private static final String FAIL_ISSUE_BODY = "%s 쿠폰 발행을 실패했습니다. 다음 기회에!";
private static final long ISSUE_SIZE = 10;
private static final long ISSUE_FIRST = 0;

private final ClockHolder clockHolder;
private final NotificationService notificationService;
Expand All @@ -37,13 +37,6 @@ public class CouponManageService {
private final CouponManageRepository couponManageRepository;
private final CouponWalletRepository couponWalletRepository;

private long current = ISSUE_FIRST;

@Scheduled(cron = "0 0 0 * * *")
public void init() {
current = ISSUE_FIRST;
}

@Scheduled(fixedDelay = 1000)
public void issue() {
LocalDate now = clockHolder.date();
Expand All @@ -55,39 +48,48 @@ public void issue() {

Coupon coupon = optionalCoupon.get();
String couponName = coupon.getName();
int max = coupon.getStock();
int maxCount = coupon.getMaxCount();
int currentCount = couponManageRepository.getCouponCount(couponName);

Set<Long> membersId = couponManageRepository.rangeQueue(couponName, current, current + ISSUE_SIZE);

for (Long memberId : membersId) {
int rank = couponManageRepository.rankQueue(couponName, memberId);
if (maxCount <= currentCount) {
return;
}

if (max < rank) {
notificationService.sendCouponIssueResult(memberId, couponName, FAIL_ISSUE_BODY);
continue;
}
Set<Long> membersId = couponManageRepository.rangeQueue(couponName, currentCount, currentCount + ISSUE_SIZE);

for (Long memberId : membersId) {
couponWalletRepository.save(CouponWallet.create(memberId, coupon));
notificationService.sendCouponIssueResult(memberId, couponName, SUCCESS_ISSUE_BODY);
current++;
}

couponManageRepository.increase(couponName, membersId.size());
}

public void registerQueue(String couponName, Long memberId) {
double registerTime = System.currentTimeMillis();
validateRegisterQueue(couponName);
validateRegisterQueue(couponName, memberId);
couponManageRepository.addIfAbsentQueue(couponName, memberId, registerTime);
}

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

private void validateRegisterQueue(String couponName) {
private void validateRegisterQueue(String couponName, Long memberId) {
LocalDate now = clockHolder.date();
Coupon coupon = couponRepository.findByNameAndStartAt(couponName, now)
.orElseThrow(() -> new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD));

if (couponManageRepository.hasValue(couponName, memberId)) {
throw new ConflictException(ErrorMessage.CONFLICT_COUPON_ISSUE);
}

int maxCount = coupon.getMaxCount();
int sizeQueue = couponManageRepository.sizeQueue(couponName);

if (!couponRepository.existsByNameAndStartAt(couponName, now)) {
throw new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD);
if (maxCount <= sizeQueue) {
notificationService.sendCouponIssueResult(memberId, couponName, FAIL_ISSUE_BODY);
throw new BadRequestException(ErrorMessage.INVALID_COUPON_STOCK_END);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static Coupon toEntity(Long adminId, CreateCouponRequest coupon) {
.description(coupon.description())
.type(CouponType.from(coupon.type()))
.point(coupon.point())
.stock(coupon.stock())
.maxCount(coupon.maxCount())
.startAt(coupon.startAt())
.openAt(coupon.openAt())
.adminId(adminId)
Expand All @@ -33,11 +33,11 @@ public static Coupon toEntity(Long adminId, CreateCouponRequest coupon) {
public static CouponResponse toResponse(Coupon coupon) {
return CouponResponse.builder()
.id(coupon.getId())
.adminName(coupon.getAdminId() + "admin")
.adminName("ID : " + coupon.getAdminId())
.name(coupon.getName())
.description(coupon.getDescription())
.point(coupon.getPoint())
.stock(coupon.getStock())
.maxCount(coupon.getMaxCount())
.type(coupon.getType())
.startAt(coupon.getStartAt())
.openAt(coupon.getOpenAt())
Expand Down
23 changes: 11 additions & 12 deletions src/main/java/com/moabam/api/domain/coupon/Coupon.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public class Coupon extends BaseTimeEntity {
@Column(name = "point", nullable = false)
private int point;

@ColumnDefault("1")
@Column(name = "max_count", nullable = false)
private int maxCount;

@ColumnDefault("''")
@Column(name = "description", length = 50)
private String description;
Expand All @@ -51,33 +55,33 @@ public class Coupon extends BaseTimeEntity {
@Column(name = "type", nullable = false)
private CouponType type;

@ColumnDefault("1")
@Column(name = "stock", nullable = false)
private int stock;

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

@Column(name = "open_at", nullable = false)
private LocalDate openAt;

// TODO : 관리자 테이블 생기면 관리자 테이블이랑 다대일 관계 맺을 예정
@Column(name = "admin_id", updatable = false, nullable = false)
private Long adminId;

@Builder
private Coupon(String name, String description, int point, int stock, CouponType type, LocalDate startAt,
private Coupon(String name, String description, int point, int maxCount, CouponType type, LocalDate startAt,
LocalDate openAt, Long adminId) {
this.name = requireNonNull(name);
this.point = validatePoint(point);
this.maxCount = validateStock(maxCount);
this.description = Optional.ofNullable(description).orElse(BLANK);
this.type = requireNonNull(type);
this.stock = validateStock(stock);
this.startAt = requireNonNull(startAt);
this.openAt = requireNonNull(openAt);
this.adminId = requireNonNull(adminId);
}

@Override
public String toString() {
return String.format("Coupon{startAt=%s, openAt=%s}", startAt, openAt);
}

private int validatePoint(int point) {
if (point < 1) {
throw new BadRequestException(INVALID_COUPON_POINT);
Expand All @@ -93,9 +97,4 @@ private int validateStock(int stock) {

return stock;
}

@Override
public String toString() {
return String.format("Coupon{startAt=%s, openAt=%s}", startAt, openAt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static java.util.Objects.*;

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

Expand All @@ -16,6 +17,7 @@
@RequiredArgsConstructor
public class CouponManageRepository {

private static final String COUPON_COUNT_KEY = "%s_COUPON_COUNT_KEY";
private static final int EXPIRE_DAYS = 2;

private final ZSetRedisRepository zSetRedisRepository;
Expand All @@ -31,20 +33,39 @@ public void addIfAbsentQueue(String couponName, Long memberId, double registerTi
}

public Set<Long> rangeQueue(String couponName, long start, long end) {

return zSetRedisRepository
.range(requireNonNull(couponName), start, end)
.stream()
.map(memberId -> Long.parseLong(String.valueOf(memberId)))
.collect(Collectors.toSet());
}

public boolean hasValue(String couponName, Long memberId) {
return Objects.nonNull(zSetRedisRepository.score(couponName, memberId));
}

public int sizeQueue(String couponName) {
return zSetRedisRepository
.size(couponName)
.intValue();
}

public int rankQueue(String couponName, Long memberId) {
return zSetRedisRepository
.rank(requireNonNull(couponName), requireNonNull(memberId))
.intValue();
}

public int getCouponCount(String couponName) {
String couponCountKey = String.format(COUPON_COUNT_KEY, couponName);
return Integer.parseInt(valueRedisRepository.get(couponCountKey));
}

public void increase(String couponName, long count) {
String couponCountKey = String.format(COUPON_COUNT_KEY, couponName);
valueRedisRepository.increment(couponCountKey, count);
}

public void deleteQueue(String couponName) {
valueRedisRepository.delete(requireNonNull(couponName));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public interface CouponRepository extends JpaRepository<Coupon, Long> {

Optional<Coupon> findByStartAt(LocalDate startAt);

Optional<Coupon> findByNameAndStartAt(String couponName, LocalDate startAt);

boolean existsByNameAndStartAt(String couponName, LocalDate startAt);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public record CouponResponse(
String name,
String description,
int point,
int stock,
int maxCount,
CouponType type,
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate startAt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public record CreateCouponRequest(
@Length(max = 50, message = "쿠폰 간단 소개는 최대 50자까지 가능합니다.") String description,
@NotBlank(message = "쿠폰 종류를 입력해주세요.") String type,
@Min(value = 1, message = "벌레 수 혹은 할인 금액은 1 이상이어야 합니다.") int point,
@Min(value = 1, message = "쿠폰 재고는 1 이상이어야 합니다.") int stock,
@Min(value = 1, message = "쿠폰 최대 갯수는 1 이상이어야 합니다.") int maxCount,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
@NotNull(message = "쿠폰 발급이 가능한 날짜(년, 월, 일)를 입력해주세요.") LocalDate startAt,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public void save(String key, String value, Duration timeout) {
.set(key, value, timeout);
}

public Long increment(String key) {
public Long increment(String key, long delta) {
return redisTemplate
.opsForValue()
.increment(key);
.increment(key, delta);
}

public String get(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ public Long rank(String key, Object value) {
.rank(key, value);
}

public Double score(String key, Object value) {
return redisTemplate
.opsForZSet()
.score(key, value);
}

public Long size(String key) {
return redisTemplate
.opsForZSet()
.zCard(key);
}

public void add(String key, Object value, double score) {
redisTemplate
.opsForZSet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public enum ErrorMessage {
INVALID_BUG_COUPON("벌레 쿠폰은 보관함에서 사용할 수 있습니다."),
CONFLICT_COUPON_NAME("쿠폰의 이름이 중복되었습니다."),
CONFLICT_COUPON_START_AT("쿠폰 발급 가능 날짜가 중복되었습니다."),
CONFLICT_COUPON_ISSUE("이미 쿠폰 발급에 성공했습니다!"),
NOT_FOUND_COUPON_TYPE("존재하지 않는 쿠폰 종류입니다."),
NOT_FOUND_COUPON("존재하지 않는 쿠폰입니다."),
NOT_FOUND_COUPON_WALLET("보유하지 않은 쿠폰입니다."),
Expand Down
24 changes: 12 additions & 12 deletions src/main/resources/static/docs/coupon.html
Original file line number Diff line number Diff line change
Expand Up @@ -461,15 +461,15 @@ <h4 id="_요청" class="discrete">요청</h4>
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">POST /admins/coupons HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 175
Content-Length: 178
Host: localhost:8080

{
"name" : "couponName",
"description" : "coupon description",
"type" : "황금",
"point" : 10,
"stock" : 10,
"maxCount" : 10,
"startAt" : "2023-02-01",
"openAt" : "2023-01-01"
}</code></pre>
Expand Down Expand Up @@ -498,7 +498,7 @@ <h3 id="_쿠폰_삭제">쿠폰 삭제</h3>
<h4 id="_요청_2" class="discrete">요청</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">DELETE /admins/coupons/35 HTTP/1.1
<pre class="highlight nowrap"><code class="language-http" data-lang="http">DELETE /admins/coupons/34 HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
Expand Down Expand Up @@ -526,7 +526,7 @@ <h3 id="_특정_쿠폰_조회">특정 쿠폰 조회</h3>
<h4 id="_요청_3">요청</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /coupons/23 HTTP/1.1
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /coupons/22 HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
Expand All @@ -540,15 +540,15 @@ <h4 id="_응답_3" class="discrete">응답</h4>
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Content-Type: application/json
Content-Length: 198
Content-Length: 201

{
"id" : 23,
"adminName" : "1admin",
"id" : 22,
"adminName" : "ID : 1",
"name" : "couponName",
"description" : "",
"point" : 10,
"stock" : 100,
"maxCount" : 100,
"type" : "MORNING",
"startAt" : "2023-02-01",
"openAt" : "2023-01-01"
Expand Down Expand Up @@ -590,15 +590,15 @@ <h4 id="_응답_4" class="discrete">응답</h4>
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Content-Type: application/json
Content-Length: 199
Content-Length: 202

[ {
"id" : 24,
"adminName" : "1admin",
"id" : 23,
"adminName" : "ID : 1",
"name" : "coupon1",
"description" : "",
"point" : 10,
"stock" : 100,
"maxCount" : 100,
"type" : "MORNING",
"startAt" : "2023-03-01",
"openAt" : "2023-01-01"
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/static/docs/notification.html
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ <h3 id="_콕_찌르기_알림">콕 찌르기 알림</h3>
<h4 id="_요청" class="discrete">요청</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /notifications/rooms/4/members/4 HTTP/1.1
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /notifications/rooms/1/members/2 HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
Expand Down
Loading

0 comments on commit 0fd11b3

Please sign in to comment.