From 105f7781b37ba26ed6a946b7b0af6251299a234c Mon Sep 17 00:00:00 2001 From: WilliamColton Date: Thu, 7 Aug 2025 16:18:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A7=AF=E5=88=86=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommentController.java | 3 + .../openisle/controller/PostController.java | 10 +- .../controller/ReactionController.java | 13 +- .../java/com/openisle/dto/CommentDto.java | 1 + .../java/com/openisle/dto/PostSummaryDto.java | 1 + .../main/java/com/openisle/dto/UserDto.java | 1 + .../java/com/openisle/mapper/UserMapper.java | 1 + .../java/com/openisle/model/PointLog.java | 37 +++++ .../main/java/com/openisle/model/User.java | 6 +- .../repository/PointLogRepository.java | 12 ++ .../com/openisle/service/PointService.java | 115 +++++++++++++++ frontend/src/views/NewPostPageView.vue | 67 ++++++--- frontend/src/views/PostPageView.vue | 132 ++++++++++-------- 13 files changed, 306 insertions(+), 93 deletions(-) create mode 100644 backend/src/main/java/com/openisle/model/PointLog.java create mode 100644 backend/src/main/java/com/openisle/repository/PointLogRepository.java create mode 100644 backend/src/main/java/com/openisle/service/PointService.java diff --git a/backend/src/main/java/com/openisle/controller/CommentController.java b/backend/src/main/java/com/openisle/controller/CommentController.java index e0837379c..01669ea61 100644 --- a/backend/src/main/java/com/openisle/controller/CommentController.java +++ b/backend/src/main/java/com/openisle/controller/CommentController.java @@ -7,6 +7,7 @@ import com.openisle.mapper.CommentMapper; import com.openisle.service.CaptchaService; import com.openisle.service.CommentService; import com.openisle.service.LevelService; +import com.openisle.service.PointService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -26,6 +27,7 @@ public class CommentController { private final LevelService levelService; private final CaptchaService captchaService; private final CommentMapper commentMapper; + private final PointService pointService; @Value("${app.captcha.enabled:false}") private boolean captchaEnabled; @@ -45,6 +47,7 @@ public class CommentController { Comment comment = commentService.addComment(auth.getName(), postId, req.getContent()); CommentDto dto = commentMapper.toDto(comment); dto.setReward(levelService.awardForComment(auth.getName())); + dto.setPointReward(pointService.awardForComment(auth.getName(),postId)); log.debug("createComment succeeded for comment {}", comment.getId()); return ResponseEntity.ok(dto); } diff --git a/backend/src/main/java/com/openisle/controller/PostController.java b/backend/src/main/java/com/openisle/controller/PostController.java index 8c1c16024..52932b53a 100644 --- a/backend/src/main/java/com/openisle/controller/PostController.java +++ b/backend/src/main/java/com/openisle/controller/PostController.java @@ -5,12 +5,7 @@ import com.openisle.dto.PostRequest; import com.openisle.dto.PostSummaryDto; import com.openisle.mapper.PostMapper; import com.openisle.model.Post; -import com.openisle.service.CaptchaService; -import com.openisle.service.DraftService; -import com.openisle.service.LevelService; -import com.openisle.service.PostService; -import com.openisle.service.SubscriptionService; -import com.openisle.service.UserVisitService; +import com.openisle.service.*; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -25,12 +20,12 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class PostController { private final PostService postService; - private final SubscriptionService subscriptionService; private final LevelService levelService; private final CaptchaService captchaService; private final DraftService draftService; private final UserVisitService userVisitService; private final PostMapper postMapper; + private final PointService pointService; @Value("${app.captcha.enabled:false}") private boolean captchaEnabled; @@ -48,6 +43,7 @@ public class PostController { draftService.deleteDraft(auth.getName()); PostDetailDto dto = postMapper.toDetailDto(post, auth.getName()); dto.setReward(levelService.awardForPost(auth.getName())); + dto.setPointReward(pointService.awardForPost(auth.getName())); return ResponseEntity.ok(dto); } diff --git a/backend/src/main/java/com/openisle/controller/ReactionController.java b/backend/src/main/java/com/openisle/controller/ReactionController.java index e5466f7b6..5ef655e1b 100644 --- a/backend/src/main/java/com/openisle/controller/ReactionController.java +++ b/backend/src/main/java/com/openisle/controller/ReactionController.java @@ -6,8 +6,8 @@ import com.openisle.mapper.ReactionMapper; import com.openisle.model.Reaction; import com.openisle.model.ReactionType; import com.openisle.service.LevelService; +import com.openisle.service.PointService; import com.openisle.service.ReactionService; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -20,6 +20,7 @@ public class ReactionController { private final ReactionService reactionService; private final LevelService levelService; private final ReactionMapper reactionMapper; + private final PointService pointService; /** * Get all available reaction types. @@ -31,27 +32,29 @@ public class ReactionController { @PostMapping("/posts/{postId}/reactions") public ResponseEntity reactToPost(@PathVariable Long postId, - @RequestBody ReactionRequest req, - Authentication auth) { + @RequestBody ReactionRequest req, + Authentication auth) { Reaction reaction = reactionService.reactToPost(auth.getName(), postId, req.getType()); if (reaction == null) { return ResponseEntity.noContent().build(); } ReactionDto dto = reactionMapper.toDto(reaction); dto.setReward(levelService.awardForReaction(auth.getName())); + pointService.awardForReactionOfPost(auth.getName(), postId); return ResponseEntity.ok(dto); } @PostMapping("/comments/{commentId}/reactions") public ResponseEntity reactToComment(@PathVariable Long commentId, - @RequestBody ReactionRequest req, - Authentication auth) { + @RequestBody ReactionRequest req, + Authentication auth) { Reaction reaction = reactionService.reactToComment(auth.getName(), commentId, req.getType()); if (reaction == null) { return ResponseEntity.noContent().build(); } ReactionDto dto = reactionMapper.toDto(reaction); dto.setReward(levelService.awardForReaction(auth.getName())); + pointService.awardForReactionOfComment(auth.getName(), commentId); return ResponseEntity.ok(dto); } } diff --git a/backend/src/main/java/com/openisle/dto/CommentDto.java b/backend/src/main/java/com/openisle/dto/CommentDto.java index 17babe624..54c3711f0 100644 --- a/backend/src/main/java/com/openisle/dto/CommentDto.java +++ b/backend/src/main/java/com/openisle/dto/CommentDto.java @@ -17,5 +17,6 @@ public class CommentDto { private List replies; private List reactions; private int reward; + private int pointReward; } diff --git a/backend/src/main/java/com/openisle/dto/PostSummaryDto.java b/backend/src/main/java/com/openisle/dto/PostSummaryDto.java index 02d2ad76d..f77e1df59 100644 --- a/backend/src/main/java/com/openisle/dto/PostSummaryDto.java +++ b/backend/src/main/java/com/openisle/dto/PostSummaryDto.java @@ -27,5 +27,6 @@ public class PostSummaryDto { private List participants; private boolean subscribed; private int reward; + private int pointReward; } diff --git a/backend/src/main/java/com/openisle/dto/UserDto.java b/backend/src/main/java/com/openisle/dto/UserDto.java index ec3ee8afd..158340041 100644 --- a/backend/src/main/java/com/openisle/dto/UserDto.java +++ b/backend/src/main/java/com/openisle/dto/UserDto.java @@ -25,6 +25,7 @@ public class UserDto { private long likesReceived; private boolean subscribed; private int experience; + private int point; private int currentLevel; private int nextLevelExp; } diff --git a/backend/src/main/java/com/openisle/mapper/UserMapper.java b/backend/src/main/java/com/openisle/mapper/UserMapper.java index 3edca1a49..0244b151e 100644 --- a/backend/src/main/java/com/openisle/mapper/UserMapper.java +++ b/backend/src/main/java/com/openisle/mapper/UserMapper.java @@ -53,6 +53,7 @@ public class UserMapper { dto.setLikesSent(reactionService.countLikesSent(user.getUsername())); dto.setLikesReceived(reactionService.countLikesReceived(user.getUsername())); dto.setExperience(user.getExperience()); + dto.setPoint(user.getPoint()); dto.setCurrentLevel(levelService.getLevel(user.getExperience())); dto.setNextLevelExp(levelService.nextLevelExp(user.getExperience())); if (viewer != null) { diff --git a/backend/src/main/java/com/openisle/model/PointLog.java b/backend/src/main/java/com/openisle/model/PointLog.java new file mode 100644 index 000000000..466400331 --- /dev/null +++ b/backend/src/main/java/com/openisle/model/PointLog.java @@ -0,0 +1,37 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; + +/** Daily experience gain counts for a user. */ +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "point_logs", + uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "log_date"})) +public class PointLog { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id") + private User user; + + @Column(name = "log_date", nullable = false) + private LocalDate logDate; + + @Column(name = "post_count", nullable = false) + private int postCount; + + @Column(name = "comment_count", nullable = false) + private int commentCount; + + @Column(name = "reaction_count", nullable = false) + private int reactionCount; +} diff --git a/backend/src/main/java/com/openisle/model/User.java b/backend/src/main/java/com/openisle/model/User.java index 16ae8a90c..8a17dfa85 100644 --- a/backend/src/main/java/com/openisle/model/User.java +++ b/backend/src/main/java/com/openisle/model/User.java @@ -5,9 +5,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.CreationTimestamp; -import java.time.LocalDateTime; -import com.openisle.model.Role; +import java.time.LocalDateTime; /** * Simple user entity with basic fields and a role. @@ -44,6 +43,9 @@ public class User { @Column(nullable = false) private int experience = 0; + @Column(nullable = false) + private int point = 0; + @Column(length = 1000) private String introduction; diff --git a/backend/src/main/java/com/openisle/repository/PointLogRepository.java b/backend/src/main/java/com/openisle/repository/PointLogRepository.java new file mode 100644 index 000000000..4f67aca2e --- /dev/null +++ b/backend/src/main/java/com/openisle/repository/PointLogRepository.java @@ -0,0 +1,12 @@ +package com.openisle.repository; + +import com.openisle.model.PointLog; +import com.openisle.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDate; +import java.util.Optional; + +public interface PointLogRepository extends JpaRepository { + Optional findByUserAndLogDate(User user, LocalDate logDate); +} diff --git a/backend/src/main/java/com/openisle/service/PointService.java b/backend/src/main/java/com/openisle/service/PointService.java new file mode 100644 index 000000000..78d802790 --- /dev/null +++ b/backend/src/main/java/com/openisle/service/PointService.java @@ -0,0 +1,115 @@ +package com.openisle.service; + +import com.openisle.model.PointLog; +import com.openisle.model.User; +import com.openisle.repository.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; + +@Service +@RequiredArgsConstructor +public class PointService { + + private final UserRepository userRepository; + private final PointLogRepository pointLogRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + + public int awardForPost(String userName) { + User user = userRepository.findByUsername(userName).orElseThrow(); + PointLog log = getTodayLog(user); + if (log.getPostCount() > 1) return 0; + log.setPostCount(log.getPostCount() + 1); + pointLogRepository.save(log); + return addPoint(user, 30); + } + + private PointLog getTodayLog(User user) { + LocalDate today = LocalDate.now(); + return pointLogRepository.findByUserAndLogDate(user, today) + .orElseGet(() -> { + PointLog log = new PointLog(); + log.setUser(user); + log.setLogDate(today); + log.setPostCount(0); + log.setCommentCount(0); + log.setReactionCount(0); + return pointLogRepository.save(log); + }); + } + + private int addPoint(User user, int amount) { + user.setPoint(user.getPoint() + amount); + userRepository.save(user); + return amount; + } + + // 同时为评论者和发帖人增加积分,返回值为评论者增加的积分数 + // 注意需要考虑发帖和回复是同一人的场景 + public int awardForComment(String commenterName, Long postId) { + // 标记评论者是否已达到积分奖励上限 + boolean isTheRewardCapped = false; + + // 根据帖子id找到发帖人 + User poster = postRepository.findById(postId).orElseThrow().getAuthor(); + + // 获取评论者的加分日志 + User commenter = userRepository.findByUsername(commenterName).orElseThrow(); + PointLog log = getTodayLog(commenter); + if (log.getCommentCount() > 3) { + isTheRewardCapped = true; + } + + // 如果发帖人与评论者是同一个,则只计算发帖加分 + if (poster.getId().equals(commenter.getId())) { + if (isTheRewardCapped) { + return 0; + } else { + log.setCommentCount(log.getCommentCount() + 1); + pointLogRepository.save(log); + return addPoint(commenter, 10); + } + } + + // 如果不是同一个,则为发帖人和评论者同时加分 + addPoint(poster, 10); + return addPoint(commenter, 10); + } + + // 考虑点赞者和发帖人是同一个的情况 + public int awardForReactionOfPost(String reactionerName, Long postId) { + // 根据帖子id找到发帖人 + User poster = postRepository.findById(postId).orElseThrow().getAuthor(); + + // 获取点赞者信息 + User reactioner = userRepository.findByUsername(reactionerName).orElseThrow(); + + // 如果发帖人与点赞者是同一个,则不加分 + if (poster.getId().equals(reactioner.getId())) { + return 0; + } + + // 如果不是同一个,则为发帖人加分 + return addPoint(poster, 10); + } + + // 考虑点赞者和评论者是同一个的情况 + public int awardForReactionOfComment(String reactionerName, Long commentId) { + // 根据帖子id找到评论者 + User commenter = commentRepository.findById(commentId).orElseThrow().getAuthor(); + + // 获取点赞者信息 + User reactioner = userRepository.findByUsername(reactionerName).orElseThrow(); + + // 如果评论者与点赞者是同一个,则不加分 + if (commenter.getId().equals(reactioner.getId())) { + return 0; + } + + // 如果不是同一个,则为发帖人加分 + return addPoint(commenter, 10); + } + +} diff --git a/frontend/src/views/NewPostPageView.vue b/frontend/src/views/NewPostPageView.vue index 1038df159..0f4fe3a92 100644 --- a/frontend/src/views/NewPostPageView.vue +++ b/frontend/src/views/NewPostPageView.vue @@ -1,15 +1,15 @@ @@ -303,7 +324,6 @@ export default { } - .post-clear { color: var(--primary-color); cursor: pointer; @@ -331,6 +351,7 @@ export default { .post-submit:hover { background-color: var(--primary-color-hover); } + .post-submit.disabled:hover { background-color: var(--primary-color-disabled); } diff --git a/frontend/src/views/PostPageView.vue b/frontend/src/views/PostPageView.vue index 62c5d118d..8a247d6ef 100644 --- a/frontend/src/views/PostPageView.vue +++ b/frontend/src/views/PostPageView.vue @@ -8,8 +8,8 @@
{{ title }}
@@ -64,12 +64,12 @@
+ :show-login-overlay="!loggedIn"/>
-
Sort by:
- +
Sort by:
+
@@ -77,10 +77,10 @@
- +
@@ -92,7 +92,7 @@
{{ scrollerTopTime }}
+ @input="onSliderInput"/>
{{ currentIndex }}/{{ totalPosts }}
loading...
@@ -100,14 +100,14 @@ + @hide="lightboxVisible = false"/>