diff --git a/backend/src/main/java/com/openisle/controller/PostController.java b/backend/src/main/java/com/openisle/controller/PostController.java index 3ea7334e2..aa1707ff1 100644 --- a/backend/src/main/java/com/openisle/controller/PostController.java +++ b/backend/src/main/java/com/openisle/controller/PostController.java @@ -41,7 +41,8 @@ public class PostController { Post post = postService.createPost(auth.getName(), req.getCategoryId(), req.getTitle(), req.getContent(), req.getTagIds(), req.getType(), req.getPrizeDescription(), req.getPrizeIcon(), - req.getPrizeCount(), req.getStartTime(), req.getEndTime()); + req.getPrizeCount(), req.getPointCost(), + req.getStartTime(), req.getEndTime()); draftService.deleteDraft(auth.getName()); PostDetailDto dto = postMapper.toDetailDto(post, auth.getName()); dto.setReward(levelService.awardForPost(auth.getName())); diff --git a/backend/src/main/java/com/openisle/dto/LotteryDto.java b/backend/src/main/java/com/openisle/dto/LotteryDto.java index db6728993..73b5ca7d7 100644 --- a/backend/src/main/java/com/openisle/dto/LotteryDto.java +++ b/backend/src/main/java/com/openisle/dto/LotteryDto.java @@ -10,6 +10,7 @@ public class LotteryDto { private String prizeDescription; private String prizeIcon; private int prizeCount; + private int pointCost; private LocalDateTime startTime; private LocalDateTime endTime; private List participants; diff --git a/backend/src/main/java/com/openisle/dto/PostRequest.java b/backend/src/main/java/com/openisle/dto/PostRequest.java index 014d68bf7..1fe02669a 100644 --- a/backend/src/main/java/com/openisle/dto/PostRequest.java +++ b/backend/src/main/java/com/openisle/dto/PostRequest.java @@ -23,6 +23,7 @@ public class PostRequest { private String prizeDescription; private String prizeIcon; private Integer prizeCount; + private Integer pointCost; private LocalDateTime startTime; private LocalDateTime endTime; } diff --git a/backend/src/main/java/com/openisle/mapper/PostMapper.java b/backend/src/main/java/com/openisle/mapper/PostMapper.java index d36b25e32..ad1a826da 100644 --- a/backend/src/main/java/com/openisle/mapper/PostMapper.java +++ b/backend/src/main/java/com/openisle/mapper/PostMapper.java @@ -86,6 +86,7 @@ public class PostMapper { l.setPrizeDescription(lp.getPrizeDescription()); l.setPrizeIcon(lp.getPrizeIcon()); l.setPrizeCount(lp.getPrizeCount()); + l.setPointCost(lp.getPointCost()); l.setStartTime(lp.getStartTime()); l.setEndTime(lp.getEndTime()); l.setParticipants(lp.getParticipants().stream().map(userMapper::toAuthorDto).collect(Collectors.toList())); diff --git a/backend/src/main/java/com/openisle/model/LotteryPost.java b/backend/src/main/java/com/openisle/model/LotteryPost.java index 79639a1da..f7579714c 100644 --- a/backend/src/main/java/com/openisle/model/LotteryPost.java +++ b/backend/src/main/java/com/openisle/model/LotteryPost.java @@ -26,6 +26,9 @@ public class LotteryPost extends Post { @Column(nullable = false) private int prizeCount; + @Column(nullable = false) + private int pointCost; + @Column private LocalDateTime startTime; diff --git a/backend/src/main/java/com/openisle/model/PointHistoryType.java b/backend/src/main/java/com/openisle/model/PointHistoryType.java index af03d989c..5bff37197 100644 --- a/backend/src/main/java/com/openisle/model/PointHistoryType.java +++ b/backend/src/main/java/com/openisle/model/PointHistoryType.java @@ -8,5 +8,7 @@ public enum PointHistoryType { INVITE, FEATURE, SYSTEM_ONLINE, - REDEEM + REDEEM, + LOTTERY_JOIN, + LOTTERY_REWARD } diff --git a/backend/src/main/java/com/openisle/service/PointService.java b/backend/src/main/java/com/openisle/service/PointService.java index 2b5a53060..2e5fa48b7 100644 --- a/backend/src/main/java/com/openisle/service/PointService.java +++ b/backend/src/main/java/com/openisle/service/PointService.java @@ -2,6 +2,7 @@ package com.openisle.service; import com.openisle.model.*; import com.openisle.repository.*; +import com.openisle.exception.FieldException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -39,6 +40,17 @@ public class PointService { return addPoint(user, 500, PointHistoryType.FEATURE, post, null, null); } + public void processLotteryJoin(User participant, LotteryPost post) { + int cost = post.getPointCost(); + if (cost > 0) { + if (participant.getPoint() < cost) { + throw new FieldException("point", "积分不足"); + } + addPoint(participant, -cost, PointHistoryType.LOTTERY_JOIN, post, null, post.getAuthor()); + addPoint(post.getAuthor(), cost, PointHistoryType.LOTTERY_REWARD, post, null, participant); + } + } + private PointLog getTodayLog(User user) { LocalDate today = LocalDate.now(); return pointLogRepository.findByUserAndLogDate(user, today) diff --git a/backend/src/main/java/com/openisle/service/PostService.java b/backend/src/main/java/com/openisle/service/PostService.java index a3038d478..993c19c20 100644 --- a/backend/src/main/java/com/openisle/service/PostService.java +++ b/backend/src/main/java/com/openisle/service/PostService.java @@ -164,6 +164,7 @@ public class PostService { String prizeDescription, String prizeIcon, Integer prizeCount, + Integer pointCost, LocalDateTime startTime, LocalDateTime endTime) { long recent = postRepository.countByAuthorAfter(username, @@ -188,10 +189,14 @@ public class PostService { PostType actualType = type != null ? type : PostType.NORMAL; Post post; if (actualType == PostType.LOTTERY) { + if (pointCost != null && (pointCost < 0 || pointCost > 100)) { + throw new IllegalArgumentException("pointCost must be between 0 and 100"); + } LotteryPost lp = new LotteryPost(); lp.setPrizeDescription(prizeDescription); lp.setPrizeIcon(prizeIcon); lp.setPrizeCount(prizeCount != null ? prizeCount : 0); + lp.setPointCost(pointCost != null ? pointCost : 0); lp.setStartTime(startTime); lp.setEndTime(endTime); post = lp; @@ -250,8 +255,10 @@ public class PostService { .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); User user = userRepository.findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); - post.getParticipants().add(user); - lotteryPostRepository.save(post); + if (post.getParticipants().add(user)) { + pointService.processLotteryJoin(user, post); + lotteryPostRepository.save(post); + } } @Transactional diff --git a/backend/src/main/resources/db/migration/V3__add_point_cost_to_lottery_posts.sql b/backend/src/main/resources/db/migration/V3__add_point_cost_to_lottery_posts.sql new file mode 100644 index 000000000..115689a5c --- /dev/null +++ b/backend/src/main/resources/db/migration/V3__add_point_cost_to_lottery_posts.sql @@ -0,0 +1 @@ +ALTER TABLE lottery_posts ADD COLUMN point_cost INT NOT NULL DEFAULT 0; diff --git a/backend/src/test/java/com/openisle/controller/PostControllerTest.java b/backend/src/test/java/com/openisle/controller/PostControllerTest.java index 7b4fc2265..90779b57e 100644 --- a/backend/src/test/java/com/openisle/controller/PostControllerTest.java +++ b/backend/src/test/java/com/openisle/controller/PostControllerTest.java @@ -76,7 +76,7 @@ class PostControllerTest { post.setTags(Set.of(tag)); when(postService.createPost(eq("alice"), eq(1L), eq("t"), eq("c"), eq(List.of(1L)), - isNull(), isNull(), isNull(), isNull(), isNull(), isNull())).thenReturn(post); + isNull(), isNull(), isNull(), isNull(), isNull(), isNull(), isNull())).thenReturn(post); when(postService.viewPost(eq(1L), any())).thenReturn(post); when(commentService.getCommentsForPost(eq(1L), any())).thenReturn(List.of()); when(commentService.getParticipants(anyLong(), anyInt())).thenReturn(List.of()); @@ -187,7 +187,7 @@ class PostControllerTest { .andExpect(status().isBadRequest()); verify(postService, never()).createPost(any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any()); + any(), any(), any(), any(), any(), any(), any()); } @Test diff --git a/backend/src/test/java/com/openisle/service/PostServiceTest.java b/backend/src/test/java/com/openisle/service/PostServiceTest.java index 6abe97238..a7bf71caa 100644 --- a/backend/src/test/java/com/openisle/service/PostServiceTest.java +++ b/backend/src/test/java/com/openisle/service/PostServiceTest.java @@ -146,7 +146,7 @@ class PostServiceTest { assertThrows(RateLimitException.class, () -> service.createPost("alice", 1L, "t", "c", List.of(1L), - null, null, null, null, null, null)); + null, null, null, null, null, null, null)); } @Test diff --git a/frontend_nuxt/pages/new-post.vue b/frontend_nuxt/pages/new-post.vue index 533f2faf0..e66c56353 100644 --- a/frontend_nuxt/pages/new-post.vue +++ b/frontend_nuxt/pages/new-post.vue @@ -66,6 +66,18 @@ /> +
+ 参与所需积分 +
+ +
+
抽奖结束时间 @@ -105,6 +117,7 @@ const showPrizeCropper = ref(false) const prizeName = ref('') const prizeCount = ref(1) const prizeDescription = ref('') +const pointCost = ref(0) const endTime = ref(null) const startTime = ref(null) const dateConfig = { enableTime: true, time_24hr: true, dateFormat: 'Y-m-d H:i' } @@ -133,6 +146,11 @@ watch(prizeCount, (val) => { if (!val || val < 1) prizeCount.value = 1 }) +watch(pointCost, (val) => { + if (val === undefined || val === null || val < 0) pointCost.value = 0 + if (val > 100) pointCost.value = 100 +}) + const loadDraft = async () => { const token = getToken() if (!token) return @@ -168,6 +186,7 @@ const clearPost = async () => { showPrizeCropper.value = false prizeDescription.value = '' prizeCount.value = 1 + pointCost.value = 0 endTime.value = null startTime.value = null @@ -315,6 +334,10 @@ const submitPost = async () => { toast.error('请选择抽奖结束时间') return } + if (pointCost.value < 0 || pointCost.value > 100) { + toast.error('参与积分需在0到100之间') + return + } } try { const token = getToken() @@ -354,6 +377,7 @@ const submitPost = async () => { prizeDescription: postType.value === 'LOTTERY' ? prizeDescription.value : undefined, startTime: postType.value === 'LOTTERY' ? new Date(startTime.value).toISOString() : undefined, + pointCost: postType.value === 'LOTTERY' ? pointCost.value : undefined, // 将时间转换为 UTC+8.5 时区 todo: 需要优化 endTime: postType.value === 'LOTTERY' diff --git a/frontend_nuxt/pages/points.vue b/frontend_nuxt/pages/points.vue index 8dd37182f..58df2f0ae 100644 --- a/frontend_nuxt/pages/points.vue +++ b/frontend_nuxt/pages/points.vue @@ -146,6 +146,24 @@ + + 你目前的积分是 {{ item.balance }}
diff --git a/frontend_nuxt/pages/posts/[id]/index.vue b/frontend_nuxt/pages/posts/[id]/index.vue index d218eb05f..e281a9d30 100644 --- a/frontend_nuxt/pages/posts/[id]/index.vue +++ b/frontend_nuxt/pages/posts/[id]/index.vue @@ -119,7 +119,7 @@ class="join-prize-button" @click="joinLottery" > -
参与抽奖
+
参与抽奖({{ lottery.pointCost }}积分)
已参与
@@ -134,7 +134,7 @@ class="join-prize-button" @click="joinLottery" > -
参与抽奖
+
参与抽奖({{ lottery.pointCost }}积分)
已参与
@@ -810,11 +810,12 @@ const joinLottery = async () => { method: 'POST', headers: { Authorization: `Bearer ${token}` }, }) + const data = await res.json().catch(() => ({})) if (res.ok) { toast.success('已参与抽奖') await refreshPost() } else { - toast.error('操作失败') + toast.error(data.error || '操作失败') } }