-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] 별점 EMA 도입 #189
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] 별점 EMA 도입 #189
Changes from all commits
60cbb0c
7c5f5a2
78cb267
bfbe9a7
e10a9c8
01e34f6
0950c66
101dcbf
354974a
ee8f447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package org.sopt.poti.domain.review.application; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.sopt.poti.domain.groupbuy.entity.GroupBuyPost; | ||
| import org.sopt.poti.domain.groupbuy.service.GroupBuyService; | ||
| import org.sopt.poti.domain.order.entity.Order; | ||
| import org.sopt.poti.domain.order.service.OrderService; | ||
| import org.sopt.poti.domain.review.dto.request.ReviewRequest; | ||
| import org.sopt.poti.domain.review.service.ReviewService; | ||
| import org.sopt.poti.domain.user.service.UserService; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional | ||
| public class ReviewApplicationService { | ||
|
|
||
| private final OrderService orderService; | ||
| private final ReviewService reviewService; | ||
| private final UserService userService; | ||
| private final GroupBuyService groupBuyService; | ||
|
|
||
| @Transactional | ||
| public Long createReview(Long writerUserId, ReviewRequest request) { | ||
| Long orderId = request.transactionId(); | ||
| int score = request.star(); | ||
|
|
||
| Order order = orderService.getOrderById(orderId); | ||
|
|
||
| orderService.validateDelivered(order); | ||
| orderService.validateOrderOwner(order, writerUserId); | ||
|
|
||
| Long reviewId = reviewService.createReviewEntity(writerUserId, order, score); | ||
|
|
||
| Long sellerId = order.getGroupBuyPost().getLeader().getId(); | ||
| Long postId = order.getGroupBuyPost().getId(); | ||
|
|
||
| // 1 팟 내부 단순평균 업데이트 | ||
| groupBuyService.addPostRating(postId, score); | ||
|
|
||
| // 2 최신 팟 상태 조회 및 데이터 수집 | ||
| GroupBuyPost post = groupBuyService.getPostWithLock(postId); | ||
| int reviewCount = post.getRatingCount(); | ||
| double postAvg = post.getRatingAvg(); | ||
|
|
||
| int postCount = groupBuyService.countPostsByLeader(sellerId); | ||
|
|
||
| // 3 판매자 평점 반영 | ||
| userService.applyPostContribution(sellerId, postId, postAvg, reviewCount, postCount); | ||
|
|
||
| return reviewId; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package org.sopt.poti.domain.user.entity; | ||
|
|
||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import jakarta.persistence.UniqueConstraint; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @Entity | ||
| @Table( | ||
| name = "seller_post_rating_contributions", | ||
| uniqueConstraints = @UniqueConstraint(name = "uk_seller_post", columnNames = {"seller_id", | ||
| "post_id"}) | ||
| ) | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class SellerPostRatingContribution { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(name = "seller_id", nullable = false) | ||
| private Long sellerId; | ||
|
|
||
| @Column(name = "post_id", nullable = false) | ||
| private Long postId; | ||
|
|
||
| @Column(name = "applied_avg", nullable = false) | ||
| private double appliedAvg; // 해당 post가 seller에 반영된 평균값 | ||
|
|
||
| @Column(name = "applied_weight", nullable = false) | ||
| private double appliedWeight; // 해당 post가 seller에 반영된 가중치 | ||
|
|
||
| @Builder | ||
| private SellerPostRatingContribution(Long sellerId, Long postId, double appliedAvg, | ||
| double appliedWeight) { | ||
| this.sellerId = sellerId; | ||
| this.postId = postId; | ||
| this.appliedAvg = appliedAvg; | ||
| this.appliedWeight = appliedWeight; | ||
| } | ||
|
|
||
| public static SellerPostRatingContribution create(Long sellerId, Long postId) { | ||
| return SellerPostRatingContribution.builder() | ||
| .sellerId(sellerId) | ||
| .postId(postId) | ||
| .appliedAvg(0.0) | ||
| .appliedWeight(0.0) | ||
| .build(); | ||
| } | ||
|
|
||
| public void update(double avg, double weight) { | ||
| this.appliedAvg = avg; | ||
| this.appliedWeight = weight; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,18 @@ public class User extends BaseSoftDeleteEntity { | |
| @Column(name = "rating_avg") | ||
| private Double ratingAvg; | ||
|
|
||
| @Column(name = "rating_sum", nullable = false) | ||
| private long ratingSum = 0L; | ||
|
|
||
| @Column(name = "rating_count", nullable = false) | ||
| private int ratingCount = 0; | ||
|
Comment on lines
+59
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for ratingSum and ratingCount usages across the codebase
echo "=== Searching for ratingSum usages ==="
rg -n --type=java 'ratingSum' -C 2
echo ""
echo "=== Searching for ratingCount usages ==="
rg -n --type=java 'ratingCount' -C 2Repository: team-poti/POTI-SERVER Length of output: 3467
이 두 필드는 선언만 되어 있고 User 클래스의 어떤 메서드에서도 실제로 사용되지 않고 있네요. 흥미롭게도 혹시 다른 레이어(서비스, 리포지토리)에서 이 필드들을 사용 중이라면 괜찮지만, 엔티티 단에서 전혀 사용되지 않는다면 불필요한 DB 컬럼 추가가 될 수 있습니다. 한 번 확인해 보시는 게 좋을 것 같아요. 🤖 Prompt for AI Agents |
||
|
|
||
| @Column(name = "rating_weighted_sum", nullable = false) | ||
| private double ratingWeightedSum = 0.0; | ||
|
|
||
| @Column(name = "rating_weight_total", nullable = false) | ||
| private double ratingWeightTotal = 0.0; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| private UserStatus status; | ||
|
|
||
|
|
@@ -103,10 +115,6 @@ public void updateFavoriteArtist(Artist artist) { | |
| this.favoriteArtist = artist; | ||
| } | ||
|
|
||
| public void updateRatingAvg(double avg) { | ||
| this.ratingAvg = avg; | ||
| } | ||
|
|
||
| public void withdraw() { | ||
| this.status = UserStatus.WITHDRAWN; | ||
| this.deletedAt = LocalDateTime.now(); | ||
|
|
@@ -115,4 +123,20 @@ public void withdraw() { | |
| this.profileImageUrl = null; | ||
| this.socialId = "deleted_" + this.id + "_" + UUID.randomUUID(); // 유니크 제약 회피 | ||
| } | ||
|
|
||
| public void addRatingWeightedDelta(double deltaWeightedSum, double deltaWeight) { | ||
| this.ratingWeightedSum += deltaWeightedSum; | ||
| this.ratingWeightTotal += deltaWeight; | ||
|
|
||
| if (this.ratingWeightTotal <= 0.0) { | ||
| this.ratingWeightedSum = 0.0; | ||
| this.ratingWeightTotal = 0.0; | ||
| this.ratingAvg = 0.0; | ||
| return; | ||
| } | ||
|
|
||
| double avg = this.ratingWeightedSum / this.ratingWeightTotal; | ||
| this.ratingAvg = Math.round(avg * 10) / 10.0; | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.