Skip to content

Commit 01d65dc

Browse files
authored
[EDMT-457] 회원 포인트 시스템 구현 및 AI 생성 시 포인트 차감 기능 추가 (#78)
* [feat] add point column to Member entity and initialize with default value * [feat] implement point validation for member actions * [feat] implement point deduction logic in Member and PointService * [feat] implement pessimistic locking for point deduction in PointService * [refac] update deductPoints method to return Member object in PointService
1 parent 3013dc7 commit 01d65dc

File tree

6 files changed

+60
-3
lines changed

6 files changed

+60
-3
lines changed

edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.edukit.common.annotation.AIGenerationMetrics;
44
import com.edukit.core.member.db.entity.Member;
55
import com.edukit.core.member.service.MemberService;
6+
import com.edukit.core.member.service.PointService;
67
import com.edukit.core.studentrecord.db.entity.StudentRecord;
78
import com.edukit.core.studentrecord.db.entity.StudentRecordAITask;
89
import com.edukit.core.studentrecord.service.AITaskService;
@@ -22,16 +23,20 @@
2223
public class StudentRecordAIFacade {
2324

2425
private final MemberService memberService;
26+
private final PointService pointService;
2527
private final AITaskService aiTaskService;
2628
private final StudentRecordService studentRecordService;
2729
private final SSEChannelManager sseChannelManager;
2830
private final ApplicationEventPublisher eventPublisher;
2931

32+
private static final int DEDUCTED_POINTS = 100;
33+
3034
@Transactional
3135
@AIGenerationMetrics
3236
public StudentRecordTaskResponse createTaskId(final long memberId, final long recordId, final int byteCount,
3337
final String userPrompt) {
34-
Member member = memberService.getMemberById(memberId);
38+
Member member = pointService.deductPoints(memberId, DEDUCTED_POINTS);
39+
3540
StudentRecord studentRecord = studentRecordService.getRecordDetail(memberId, recordId);
3641

3742
String requestPrompt = AIPromptGenerator.createStreamingPrompt(studentRecord.getStudentRecordType(), byteCount, userPrompt);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- Add point column to member table
2+
alter table member
3+
add column point int not null default 1000;

edukit-core/src/main/java/com/edukit/core/member/db/entity/Member.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public class Member extends BaseTimeEntity {
5858
@Enumerated(EnumType.STRING)
5959
private MemberRole role;
6060

61+
@Column(nullable = false)
62+
private int point;
63+
6164
@Column
6265
private LocalDateTime verifiedAt;
6366

@@ -66,16 +69,19 @@ public class Member extends BaseTimeEntity {
6669

6770
private LocalDateTime deletedAt;
6871

72+
private static final int INITIAL_POINT = 1000;
73+
6974
@Builder(access = AccessLevel.PRIVATE)
7075
private Member(final Subject subject, final String email, final String password, final String nickname,
7176
final School school, final MemberRole role, final LocalDateTime verifiedAt, final boolean isDeleted,
72-
final LocalDateTime deletedAt, final String memberUuid) {
77+
final LocalDateTime deletedAt, final String memberUuid, final int point) {
7378
this.subject = subject;
7479
this.email = email;
7580
this.password = password;
7681
this.nickname = nickname;
7782
this.school = school;
7883
this.role = role;
84+
this.point = point;
7985
this.verifiedAt = verifiedAt;
8086
this.isDeleted = isDeleted;
8187
this.deletedAt = deletedAt;
@@ -92,6 +98,7 @@ public static Member create(final Subject subject, final String email, final Str
9298
.school(school)
9399
.role(memberRole)
94100
.isDeleted(false)
101+
.point(INITIAL_POINT)
95102
.memberUuid(UUID.randomUUID().toString())
96103
.build();
97104
}
@@ -139,4 +146,8 @@ public void updateEmailAndChangeVerifyStatus(final String email) {
139146
this.email = email;
140147
this.role = MemberRole.PENDING_TEACHER;
141148
}
149+
150+
public void deductPoints(final int pointsToDeduct) {
151+
this.point -= pointsToDeduct;
152+
}
142153
}

edukit-core/src/main/java/com/edukit/core/member/db/repository/MemberRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.edukit.core.member.db.repository;
22

33
import com.edukit.core.member.db.entity.Member;
4+
import jakarta.persistence.LockModeType;
45
import java.util.Optional;
56
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Lock;
68
import org.springframework.data.jpa.repository.Query;
79
import org.springframework.data.repository.query.Param;
810

@@ -20,4 +22,8 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
2022

2123
@Query("SELECT m FROM Member m JOIN FETCH m.subject WHERE m.id = :id AND m.isDeleted = :isDeleted")
2224
Optional<Member> findByIdAndIsDeletedFetchJoinSubject(@Param("id") long id, @Param("isDeleted") boolean isDeleted);
25+
26+
@Lock(LockModeType.PESSIMISTIC_WRITE)
27+
@Query("SELECT m FROM Member m WHERE m.id = :id AND m.isDeleted = false")
28+
Optional<Member> findByIdWithLock(@Param("id") Long id);
2329
}

edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public enum MemberErrorCode implements ErrorCode {
1515
DUPLICATED_NICKNAME("M-40005", "입력하신 닉네임은 중복된 닉네임입니다."),
1616
INVALID_CURRENT_PASSWORD("M-40006", "현재 비밀번호가 일치하지 않습니다. 다시 입력해주세요."),
1717
SAME_PASSWORD("M-40007", "새로운 비밀번호는 기존 비밀번호와 같을 수 없습니다."),
18-
DUPLICATED_EMAIL("M-40908", "이미 등록된 이메일입니다.");
18+
DUPLICATED_EMAIL("M-40908", "이미 등록된 이메일입니다."),
19+
INSUFFICIENT_POINTS("M-40009", "포인트가 부족합니다.");
1920

2021
private final String code;
2122
private final String message;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.edukit.core.member.service;
2+
3+
import com.edukit.core.member.db.entity.Member;
4+
import com.edukit.core.member.db.repository.MemberRepository;
5+
import com.edukit.core.member.exception.MemberErrorCode;
6+
import com.edukit.core.member.exception.MemberException;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
@Service
12+
@RequiredArgsConstructor
13+
public class PointService {
14+
15+
private final MemberRepository memberRepository;
16+
17+
@Transactional
18+
public Member deductPoints(final Long memberId, final int pointsToDeduct) {
19+
Member member = memberRepository.findByIdWithLock(memberId)
20+
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
21+
22+
int currentPoint = member.getPoint();
23+
24+
if (currentPoint < pointsToDeduct) {
25+
throw new MemberException(MemberErrorCode.INSUFFICIENT_POINTS);
26+
}
27+
28+
member.deductPoints(pointsToDeduct);
29+
return member;
30+
}
31+
}

0 commit comments

Comments
 (0)