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

[화살] 화살 연관 로직 수정(#68) #81

Merged
merged 39 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a5b0c81
feat: lombok 활용하도록 변경(#68)
Minjae-An Apr 20, 2024
babf288
feat: gender boolean 값 string 변환 메서드 추가(#68)
Minjae-An Apr 20, 2024
d615581
feat: 사용자 엔티티 수정(#68)
Minjae-An Apr 20, 2024
65aac96
feat: 사용자 연관 예외 추가(#68)
Minjae-An Apr 20, 2024
69bdf2f
feat: 사용자 엔티티 boolean 타입 성별 getter 사용 위치 수정(#68)
Minjae-An Apr 20, 2024
a57eb4e
feat: 휴면 상태 사용자에게 화살 보냄 예외 정의(#68)
Minjae-An Apr 20, 2024
1c8cda8
feat: 불필요한 화살 보내기 요청 DTO 삭제(#68)
Minjae-An Apr 20, 2024
c90bcf9
feat: 화살 보내기 비즈니스 로직 수정(#68)
Minjae-An Apr 20, 2024
3212b55
feat: 화살 보내기 API 수정(#68)
Minjae-An Apr 20, 2024
9729fbb
feat: 보내는 사용자, 받는 사용자 필드명 수정(#68)
Minjae-An Apr 21, 2024
138f5a3
feat: 화살 내역 엔티티 필드명 변경에 따른 사용 위치 수정(#68)
Minjae-An Apr 21, 2024
a4a6faa
refactor: 패키지 경로 이동(#68)
Minjae-An Apr 21, 2024
b5b4a04
feat: 최종 접속 일시 갱신 메서드 파라미터명 변경(#68)
Minjae-An Apr 21, 2024
b1d29b3
feat: 최종 접속 일시 갱신 로직 추가(#68)
Minjae-An Apr 21, 2024
3c34858
feat: 인증되지 않은 사용자 예외 추가(#68)
Minjae-An Apr 21, 2024
aaf7787
feat: 최종 접속 일시 갱신 인터셉터 추가(#68)
Minjae-An Apr 21, 2024
560f40b
feat: 최종 접속 일시 갱신 인터셉터 등록 로직 추가(#68)
Minjae-An Apr 21, 2024
125b0cd
feat: 출석 체크 비즈니스 로직 수정(#68)
Minjae-An Apr 21, 2024
d8bb5e0
feat: 사용자 화살 개수 응답 DTO 사용 위치 수정(#68)
Minjae-An Apr 21, 2024
f66740d
feat: 메서드 파라미터명 수정(#68)
Minjae-An Apr 22, 2024
ff3b6af
feat: 출석 체크 로직, 출석 체크 날짜 파라미터 추가(#68)
Minjae-An Apr 22, 2024
c136b40
feat: 출석 체크 API 로직, 출석 체크 날짜 생성 코드 추가(#68)
Minjae-An Apr 22, 2024
12ac0d2
feat: 화살 충전 로직, 충전일 파라미터 추가(#68)
Minjae-An Apr 23, 2024
25756fd
feat: 화살 충전 API, 충전일 생성 코드 추가(#68)
Minjae-An Apr 23, 2024
e290c4a
Merge branch 'dev' into feat/#68-daily-check-and-send-arrow
Minjae-An May 3, 2024
b47c903
feat: 최종 접속 일시 업데이트 로직, 간단한 형태로 수정(#68)
Minjae-An May 3, 2024
c3ed280
feat: 화살 내역 타입 추가(#68)
Minjae-An May 3, 2024
aa3f868
feat: 화살 내역 타입 필드 추가(#68)
Minjae-An May 3, 2024
19f3216
feat: 광고 시청 가능 여부 확인 로직, 화살 내역 타입 비교 조건 추가(#68)
Minjae-An May 3, 2024
7719b79
feat: 화살 연관 로직들 수정(#68)
Minjae-An May 3, 2024
4ab3239
Merge branch 'dev' into feat/#68-daily-check-and-send-arrow
Minjae-An May 10, 2024
a26f8e1
feat: 사용하지 않는 최종 접속 일시 업데이트 인터셉터 삭제(#68)
Minjae-An May 13, 2024
a8925cf
feat: 사용하지 않는 예외 삭제(#68)
Minjae-An May 13, 2024
4b44a1c
feat: 코드 정렬(#68)
Minjae-An May 13, 2024
dbeae6d
feat: 출석 체크 가능 여부 확인 로직 수정(#68)
Minjae-An May 13, 2024
9f555a0
feat: 사용하지 않는 최종 접속 일시 업데이트 로직 삭제(#68)
Minjae-An May 13, 2024
7afbdb6
feat: 출석 체크 로직 수정(#68)
Minjae-An May 13, 2024
ff79f51
feat: 출석 체크 API 로직 수정(#68)
Minjae-An May 13, 2024
943eb41
feat: 사용되지 않는 예외 삭제(#68)
Minjae-An May 13, 2024
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
33 changes: 15 additions & 18 deletions be/src/main/java/yeonba/be/arrow/controller/ArrowController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import yeonba.be.arrow.dto.UserArrowsResponse;
import yeonba.be.arrow.dto.request.ArrowSendRequest;
import yeonba.be.arrow.dto.response.UserArrowsResponse;
import yeonba.be.arrow.service.ArrowService;
import yeonba.be.util.CustomResponse;

Expand All @@ -38,47 +37,45 @@ public ResponseEntity<CustomResponse<UserArrowsResponse>> arrows(
}

@Operation(summary = "출석 체크", description = "출석 체크를 통해 사용자가 10개의 화살을 획득할 수 있습니다.")
@ApiResponse(responseCode = "202", description = "출석 체크 정상 처리")
@ApiResponse(responseCode = "200", description = "출석 체크 정상 처리")
@PostMapping("/daily-check")
public ResponseEntity<CustomResponse<Void>> dailyCheck(
@RequestAttribute("userId") long userId) {

arrowService.dailyCheck(userId);
LocalDate dailyCheckDay = LocalDate.now();
arrowService.dailyCheck(userId, dailyCheckDay);

return ResponseEntity
.accepted()
.ok()
.body(new CustomResponse<>());
}

@Operation(summary = "화살 보내기", description = "다른 사용자에게 화살을 보낼 수 있습니다.")
@ApiResponse(responseCode = "202", description = "화살 전송 정상 처리")
@Operation(summary = "화살 보내기", description = "다른 사용자에게 화살을 1개 보낼 수 있습니다.")
@ApiResponse(responseCode = "200", description = "화살 전송 정상 처리")
@PostMapping("/users/{userId}/arrow")
public ResponseEntity<CustomResponse<Void>> sendArrow(
@RequestAttribute("userId") long senderId,
@Parameter(description = "화살 받는 사용자 ID", example = "1")
@PathVariable("userId") long receiverId,
@RequestBody ArrowSendRequest request) {
@PathVariable("userId") long receiverId) {

arrowService.sendArrow(
senderId,
receiverId,
request);
arrowService.sendArrow(senderId, receiverId);

return ResponseEntity
.accepted()
.ok()
.body(new CustomResponse<>());
}

@Operation(summary = "화살 충전", description = "광고 시청시 화살 5개를 충전할 수 있습니다.")
@ApiResponse(responseCode = "202", description = "화살 충전 정상 처리")
@ApiResponse(responseCode = "200", description = "화살 충전 정상 처리")
@PostMapping("/users/arrows")
public ResponseEntity<CustomResponse<Void>> chargeArrows(
@RequestAttribute("userId") long userId) {

arrowService.chargeArrows(userId);
LocalDate chargeDay = LocalDate.now();
arrowService.chargeArrows(userId, chargeDay);

return ResponseEntity
.accepted()
.ok()
.body(new CustomResponse<>());
}
}
18 changes: 0 additions & 18 deletions be/src/main/java/yeonba/be/arrow/dto/request/ArrowSendRequest.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package yeonba.be.arrow.dto;
package yeonba.be.arrow.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
Expand All @@ -8,9 +8,9 @@
@AllArgsConstructor
public class UserArrowsResponse {

@Schema(
type = "number",
description = "사용자 화살 개수",
example = "10")
private int arrows;
@Schema(
type = "number",
description = "사용자 화살 개수",
example = "10")
private int arrows;
}
10 changes: 10 additions & 0 deletions be/src/main/java/yeonba/be/arrow/entity/ArrowTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
Expand All @@ -14,6 +16,7 @@
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import yeonba.be.arrow.enums.ArrowTransactionType;
import yeonba.be.user.entity.User;

@Table(name = "arrows_transactions")
Expand All @@ -38,23 +41,30 @@ public class ArrowTransaction {
@Column(nullable = false)
private int arrows;

@Enumerated(EnumType.STRING)
private ArrowTransactionType type;

@CreatedDate
@Column(nullable = false)
private LocalDateTime createdAt;

public ArrowTransaction(
ArrowTransactionType type,
User receiver,
int arrows) {

this.type = type;
this.receiver = receiver;
this.arrows = arrows;
}

public ArrowTransaction(
ArrowTransactionType type,
User sender,
User receiver,
int arrows) {

this.type = type;
this.sender = sender;
this.receiver = receiver;
this.arrows = arrows;
Expand Down
17 changes: 17 additions & 0 deletions be/src/main/java/yeonba/be/arrow/enums/ArrowTransactionType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package yeonba.be.arrow.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ArrowTransactionType {

DAILY_CHECK("출석 체크"),

REWARDS_FOR_WATCHING_ADVERTISEMENTS("광고 시청 통한 화살 획득"),

USER_TO_USER("사용자간 송수신");

private final String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
public interface ArrowTransactionRepository extends JpaRepository<ArrowTransaction, Long>,
ArrowTransactionRepositoryCustom {

boolean existsBySenderAndReceiver(User sentUser, User receivedUser);
boolean existsBySenderAndReceiver(User sender, User receiver);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package yeonba.be.arrow.repository;

import static yeonba.be.arrow.entity.QArrowTransaction.arrowTransaction;
import static yeonba.be.arrow.enums.ArrowTransactionType.REWARDS_FOR_WATCHING_ADVERTISEMENTS;
import static yeonba.be.user.entity.QUser.user;

import com.querydsl.jpa.impl.JPAQueryFactory;
Expand All @@ -20,8 +21,9 @@ public boolean canChargeByAdvertisement(long userId, LocalDateTime today) {
queryFactory
.select(arrowTransaction.count())
.from(arrowTransaction)
.innerJoin(arrowTransaction.sender, user)
.innerJoin(arrowTransaction.receiver, user)
.where(
arrowTransaction.type.eq(REWARDS_FOR_WATCHING_ADVERTISEMENTS),
arrowTransaction.sender.isNull(),
arrowTransaction.receiver.id.eq(userId),
arrowTransaction.createdAt.after(today)
Expand Down
96 changes: 52 additions & 44 deletions be/src/main/java/yeonba/be/arrow/service/ArrowService.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,56 @@
package yeonba.be.arrow.service;

import static yeonba.be.arrow.enums.ArrowTransactionType.DAILY_CHECK;
import static yeonba.be.arrow.enums.ArrowTransactionType.REWARDS_FOR_WATCHING_ADVERTISEMENTS;
import static yeonba.be.arrow.enums.ArrowTransactionType.USER_TO_USER;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import yeonba.be.arrow.dto.UserArrowsResponse;
import yeonba.be.arrow.dto.request.ArrowSendRequest;
import yeonba.be.arrow.dto.response.UserArrowsResponse;
import yeonba.be.arrow.entity.ArrowTransaction;
import yeonba.be.arrow.repository.ArrowCommand;
import yeonba.be.arrow.repository.ArrowQuery;
import yeonba.be.exception.ArrowException;
import yeonba.be.exception.GeneralException;
import yeonba.be.exception.UserException;
import yeonba.be.user.entity.User;
import yeonba.be.user.repository.user.UserQuery;

@Service
@RequiredArgsConstructor
public class ArrowService {

private final int DAILY_CHECK_ARROW_COUNT = 10;
private final int ADVERTISEMENT_ARROW_COUNT = 5;
private final UserQuery userQuery;
private final ArrowCommand arrowCommand;
private final ArrowQuery arrowQuery;

/*
출석 체크는 다음 과정을 거쳐 이뤄진다.
1. 사용자 최종 접속 일시를 통해 이미 출석 체크하였는지 확인
2. 화살 송수신 내역 저장
3. 사용자 최종 접속 일시 갱신
4. 사용자 화살 개수 증가
*/
@Transactional
public void dailyCheck(long userId) {
public void dailyCheck(long userId, LocalDate dailyCheckDay) {

User dailyCheckUser = userQuery.findById(userId);

LocalDateTime dailyCheckedAt = LocalDateTime.now();
dailyCheckUser.validateDailyCheck(dailyCheckedAt.toLocalDate());
// 휴면 상태 사용자는 출석 체크 불가
if (dailyCheckUser.isInactive()) {
throw new GeneralException(UserException.INACTIVE_USER);
}

// 처음 가입한 사용자는 최종 접속 일시가 null, 이 경우 출석 체크를 그냥 진행함
if (!Objects.isNull(dailyCheckUser.getLastAccessedAt())) {
dailyCheckUser.validateDailyCheck(dailyCheckDay);
}

int dailyCheckArrows = 10;
ArrowTransaction arrowTransaction = new ArrowTransaction(
DAILY_CHECK,
dailyCheckUser,
DAILY_CHECK_ARROW_COUNT);
dailyCheckArrows);
arrowCommand.save(arrowTransaction);

dailyCheckUser.updateLastAccessedAt(dailyCheckedAt);
Comment on lines 56 to -48
Copy link
Contributor

Choose a reason for hiding this comment

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

최종 접속 일시 수정을 interceptor에서 하신 이유가 무엇인가요?

  • 출석체크 화살 추가와 최종 접속 일시 수정은 하나의 트랜잭션에서 동작해야 할 것같은데 이대로 문제가 없을까요?
  • 수정하신 로직으로는 화살 추가는 됐지만, 최종 접속 일시는 수정이 되지 않을 가능성이 있을 것같은데 제가 잘못 이해한 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • 출석체크가 시도될 경우, 출석체크 정상 처리여부와 관계없이 최종 접속 일시는 갱신되어야 합니다.
  • 따라서 두 작업을 한 트랜잭션으로 다루지 않았으며, 출석체크 로직에서 예외가 발생하더라도 최종 접속 일시는
    갱신되도록 인터셉터의 afterCompletion을 활용했습니다.
  • 화살 추가는 됐지만, 최종 접속 일시가 수정되지 않을 상황이 최종 접속 일시 로직의 예외 상황을 말씀하시는
    것일까요? 잘 이해하지 못해 질문드립니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

네네 사용자에게 화살은 추가됐지만 최종 접속 시간은 업데이트 되지 않은 경우는 정상적인 상황이 아닌 것같아서요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

두 작업을 한 트랜잭션으로 다룰 수 있게 변경하였습니다. 이미 출석 체크한 상황을 예외로 다루지 않는 형식으로 로직을 구성하였는데 적절한 지 한 번 피드백 주시길 부탁드립니다.

dailyCheckUser.plusArrow(DAILY_CHECK_ARROW_COUNT);
dailyCheckUser.plusArrow(dailyCheckArrows);
}

@Transactional(readOnly = true)
Expand All @@ -57,54 +61,58 @@ public UserArrowsResponse getUserArrows(long userId) {
return new UserArrowsResponse(user.getArrow());
}

/*
화살 보내기 비즈니스 로직은 다음 과정을 거친다.
1. 자기 자신에게 화살을 보내는 상황 검증
2. 화살을 보낸 사용자에게 또 보내는 상황 검증
3. 화살 내역 저장
4. 보내는 사용자 화살 감소, 화살이 부족할 경우 예외 발생
5. 받는 사용자 화살 증가
*/
@Transactional
public void sendArrow(
long senderId,
long receiverId,
ArrowSendRequest request) {
public void sendArrow(long senderId, long receiverId) {

User sender = userQuery.findById(senderId);
User receiver = userQuery.findById(receiverId);

// 휴면 상태에선 화살을 보낼 수 없음
if (sender.isInactive()) {
throw new GeneralException(UserException.INACTIVE_USER);
}

// 휴면 상태인 사용자에게 화살을 보낼 수 없음
if (receiver.isInactive()) {
throw new GeneralException(ArrowException.CAN_NOT_SEND_ARROW_TO_INACTIVE_USER);
}

// 자기 자신에게 화살을 보낼 수 없음
sender.validateNotSameUser(receiver);

// 같은 성별 사용자에게 화살을 보낼 수 없음
sender.validateSameGender(receiver);

// 이미 화살을 보낸 사용자에게 화살을 보낼 수 없음
if (arrowQuery.isArrowTransactionExist(sender, receiver)) {
throw new GeneralException(ArrowException.ALREADY_SENT_ARROW_USER);
}

int arrows = request.getArrows();
ArrowTransaction arrowTransaction = new ArrowTransaction(
sender,
receiver,
arrows);
// 화살은 1개만 보낼 수 있음
int sendArrow = 1;
ArrowTransaction arrowTransaction =
new ArrowTransaction(USER_TO_USER, sender, receiver, sendArrow);
arrowCommand.save(arrowTransaction);

sender.minusArrow(arrows);
receiver.plusArrow(arrows);
sender.minusArrow(sendArrow);
receiver.plusArrow(sendArrow);
}

@Transactional
public void chargeArrows(long userId) {
public void chargeArrows(long userId, LocalDate chargeDay) {

User user = userQuery.findById(userId);
User arrowChargeUser = userQuery.findById(userId);

LocalDateTime today = LocalDate.now().atStartOfDay();
arrowQuery.validateAdvertisementArrowCount(userId, today);
LocalDateTime chargeDayStartTime = chargeDay.atStartOfDay();
arrowQuery.validateAdvertisementArrowCount(userId, chargeDayStartTime);

int chargeArrows = 5;
ArrowTransaction arrowTransaction = new ArrowTransaction(
user,
ADVERTISEMENT_ARROW_COUNT);
REWARDS_FOR_WATCHING_ADVERTISEMENTS,
arrowChargeUser,
chargeArrows);

arrowCommand.save(arrowTransaction);
user.plusArrow(ADVERTISEMENT_ARROW_COUNT);
arrowChargeUser.plusArrow(chargeArrows);
}

}
Loading
Loading