Merge branch 'nagisa77:main' into main

This commit is contained in:
zpaeng
2025-08-21 23:54:21 +08:00
committed by GitHub
42 changed files with 1102 additions and 250 deletions

View File

@@ -5,6 +5,7 @@ import com.openisle.model.User;
import com.openisle.repository.InviteTokenRepository;
import com.openisle.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
@@ -18,6 +19,12 @@ public class InviteService {
private final JwtService jwtService;
private final PointService pointService;
@Value
public class InviteValidateResult {
InviteToken inviteToken;
boolean validate;
}
public String generate(String username) {
User inviter = userRepository.findByUsername(username).orElseThrow();
LocalDate today = LocalDate.now();
@@ -35,20 +42,23 @@ public class InviteService {
return token;
}
public boolean validate(String token) {
public InviteValidateResult validate(String token) {
if (token == null || token.isEmpty()) {
return new InviteValidateResult(null, false);
}
try {
jwtService.validateAndGetSubjectForInvite(token);
} catch (Exception e) {
return false;
return new InviteValidateResult(null, false);
}
InviteToken invite = inviteTokenRepository.findById(token).orElse(null);
return invite != null && invite.getUsageCount() < 3;
return new InviteValidateResult(invite, invite != null && invite.getUsageCount() < 3);
}
public void consume(String token) {
public void consume(String token, String newUserName) {
InviteToken invite = inviteTokenRepository.findById(token).orElseThrow();
invite.setUsageCount(invite.getUsageCount() + 1);
inviteTokenRepository.save(invite);
pointService.awardForInvite(invite.getInviter().getUsername());
pointService.awardForInvite(invite.getInviter().getUsername(), newUserName);
}
}

View File

@@ -6,6 +6,7 @@ import com.openisle.dto.MedalDto;
import com.openisle.dto.PostMedalDto;
import com.openisle.dto.SeedUserMedalDto;
import com.openisle.dto.PioneerMedalDto;
import com.openisle.dto.FeaturedMedalDto;
import com.openisle.model.MedalType;
import com.openisle.model.User;
import com.openisle.repository.CommentRepository;
@@ -74,6 +75,23 @@ public class MedalService {
postMedal.setSelected(selected == MedalType.POST);
medals.add(postMedal);
FeaturedMedalDto featuredMedal = new FeaturedMedalDto();
featuredMedal.setIcon("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/achi_rss.png");
featuredMedal.setTitle("精选作者");
featuredMedal.setDescription("至少有1篇文章被收录为精选");
featuredMedal.setType(MedalType.FEATURED);
featuredMedal.setTargetFeaturedCount(1);
if (user != null) {
long count = postRepository.countByAuthor_IdAndRssExcludedFalse(user.getId());
featuredMedal.setCurrentFeaturedCount(count);
featuredMedal.setCompleted(count >= 1);
} else {
featuredMedal.setCurrentFeaturedCount(0);
featuredMedal.setCompleted(false);
}
featuredMedal.setSelected(selected == MedalType.FEATURED);
medals.add(featuredMedal);
ContributorMedalDto contributorMedal = new ContributorMedalDto();
contributorMedal.setIcon("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/achi_coder.png");
contributorMedal.setTitle("贡献者");
@@ -141,6 +159,8 @@ public class MedalService {
user.setDisplayMedal(MedalType.COMMENT);
} else if (postRepository.countByAuthor_Id(user.getId()) >= POST_TARGET) {
user.setDisplayMedal(MedalType.POST);
} else if (postRepository.countByAuthor_IdAndRssExcludedFalse(user.getId()) >= 1) {
user.setDisplayMedal(MedalType.FEATURED);
} else if (contributorService.getContributionLines(user.getUsername()) >= CONTRIBUTION_TARGET) {
user.setDisplayMedal(MedalType.CONTRIBUTOR);
} else if (userRepository.countByCreatedAtBefore(user.getCreatedAt()) < PIONEER_LIMIT) {

View File

@@ -3,8 +3,11 @@ package com.openisle.service;
import com.openisle.exception.FieldException;
import com.openisle.exception.NotFoundException;
import com.openisle.model.PointGood;
import com.openisle.model.PointHistory;
import com.openisle.model.PointHistoryType;
import com.openisle.model.User;
import com.openisle.repository.PointGoodRepository;
import com.openisle.repository.PointHistoryRepository;
import com.openisle.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -18,6 +21,7 @@ public class PointMallService {
private final PointGoodRepository pointGoodRepository;
private final UserRepository userRepository;
private final NotificationService notificationService;
private final PointHistoryRepository pointHistoryRepository;
public List<PointGood> listGoods() {
return pointGoodRepository.findAll();
@@ -32,6 +36,13 @@ public class PointMallService {
user.setPoint(user.getPoint() - good.getCost());
userRepository.save(user);
notificationService.createPointRedeemNotifications(user, good.getName() + ": " + contact);
PointHistory history = new PointHistory();
history.setUser(user);
history.setType(PointHistoryType.REDEEM);
history.setAmount(-good.getCost());
history.setBalance(user.getPoint());
history.setCreatedAt(java.time.LocalDateTime.now());
pointHistoryRepository.save(history);
return user.getPoint();
}
}

View File

@@ -1,7 +1,6 @@
package com.openisle.service;
import com.openisle.model.PointLog;
import com.openisle.model.User;
import com.openisle.model.*;
import com.openisle.repository.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -16,19 +15,28 @@ public class PointService {
private final PointLogRepository pointLogRepository;
private final PostRepository postRepository;
private final CommentRepository commentRepository;
private final PointHistoryRepository pointHistoryRepository;
public int awardForPost(String userName) {
public int awardForPost(String userName, Long postId) {
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);
Post post = postRepository.findById(postId).orElseThrow();
return addPoint(user, 30, PointHistoryType.POST, post, null, null);
}
public int awardForInvite(String userName) {
public int awardForInvite(String userName, String inviteeName) {
User user = userRepository.findByUsername(userName).orElseThrow();
return addPoint(user, 500);
User invitee = userRepository.findByUsername(inviteeName).orElseThrow();
return addPoint(user, 500, PointHistoryType.INVITE, null, null, invitee);
}
public int awardForFeatured(String userName, Long postId) {
User user = userRepository.findByUsername(userName).orElseThrow();
Post post = postRepository.findById(postId).orElseThrow();
return addPoint(user, 500, PointHistoryType.FEATURE, post, null, null);
}
private PointLog getTodayLog(User user) {
@@ -45,20 +53,41 @@ public class PointService {
});
}
private int addPoint(User user, int amount) {
private int addPoint(User user, int amount, PointHistoryType type,
Post post, Comment comment, User fromUser) {
if (pointHistoryRepository.countByUser(user) == 0) {
recordHistory(user, PointHistoryType.SYSTEM_ONLINE, 0, null, null, null);
}
user.setPoint(user.getPoint() + amount);
userRepository.save(user);
recordHistory(user, type, amount, post, comment, fromUser);
return amount;
}
private void recordHistory(User user, PointHistoryType type, int amount,
Post post, Comment comment, User fromUser) {
PointHistory history = new PointHistory();
history.setUser(user);
history.setType(type);
history.setAmount(amount);
history.setBalance(user.getPoint());
history.setPost(post);
history.setComment(comment);
history.setFromUser(fromUser);
history.setCreatedAt(java.time.LocalDateTime.now());
pointHistoryRepository.save(history);
}
// 同时为评论者和发帖人增加积分,返回值为评论者增加的积分数
// 注意需要考虑发帖和回复是同一人的场景
public int awardForComment(String commenterName, Long postId) {
public int awardForComment(String commenterName, Long postId, Long commentId) {
// 标记评论者是否已达到积分奖励上限
boolean isTheRewardCapped = false;
// 根据帖子id找到发帖人
User poster = postRepository.findById(postId).orElseThrow().getAuthor();
Post post = postRepository.findById(postId).orElseThrow();
User poster = post.getAuthor();
Comment comment = commentRepository.findById(commentId).orElseThrow();
// 获取评论者的加分日志
User commenter = userRepository.findByUsername(commenterName).orElseThrow();
@@ -74,15 +103,15 @@ public class PointService {
} else {
log.setCommentCount(log.getCommentCount() + 1);
pointLogRepository.save(log);
return addPoint(commenter, 10);
return addPoint(commenter, 10, PointHistoryType.COMMENT, post, comment, null);
}
} else {
addPoint(poster, 10);
addPoint(poster, 10, PointHistoryType.COMMENT, post, comment, commenter);
// 如果发帖人与评论者不是同一个,则根据是否达到积分上限来判断评论者加分情况
if (isTheRewardCapped) {
return 0;
} else {
return addPoint(commenter, 10);
return addPoint(commenter, 10, PointHistoryType.COMMENT, post, comment, null);
}
}
}
@@ -101,7 +130,8 @@ public class PointService {
}
// 如果不是同一个,则为发帖人加分
return addPoint(poster, 10);
Post post = postRepository.findById(postId).orElseThrow();
return addPoint(poster, 10, PointHistoryType.POST_LIKED, post, null, reactioner);
}
// 考虑点赞者和评论者是同一个的情况
@@ -118,7 +148,17 @@ public class PointService {
}
// 如果不是同一个,则为发帖人加分
return addPoint(commenter, 10);
Comment comment = commentRepository.findById(commentId).orElseThrow();
Post post = comment.getPost();
return addPoint(commenter, 10, PointHistoryType.COMMENT_LIKED, post, comment, reactioner);
}
public java.util.List<PointHistory> listHistory(String userName) {
User user = userRepository.findByUsername(userName).orElseThrow();
if (pointHistoryRepository.countByUser(user) == 0) {
recordHistory(user, PointHistoryType.SYSTEM_ONLINE, 0, null, null, null);
}
return pointHistoryRepository.findByUserOrderByIdDesc(user);
}
}

View File

@@ -67,6 +67,7 @@ public class PostService {
private final TaskScheduler taskScheduler;
private final EmailSender emailSender;
private final ApplicationContext applicationContext;
private final PointService pointService;
private final ConcurrentMap<Long, ScheduledFuture<?>> scheduledFinalizations = new ConcurrentHashMap<>();
@Value("${app.website-url:https://www.open-isle.com}")
private String websiteUrl;
@@ -89,6 +90,7 @@ public class PostService {
TaskScheduler taskScheduler,
EmailSender emailSender,
ApplicationContext applicationContext,
PointService pointService,
@Value("${app.post.publish-mode:DIRECT}") PublishMode publishMode) {
this.postRepository = postRepository;
this.userRepository = userRepository;
@@ -107,6 +109,7 @@ public class PostService {
this.taskScheduler = taskScheduler;
this.emailSender = emailSender;
this.applicationContext = applicationContext;
this.pointService = pointService;
this.publishMode = publishMode;
}
@@ -146,7 +149,10 @@ public class PostService {
public Post includeInRss(Long id) {
Post post = postRepository.findById(id).orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found"));
post.setRssExcluded(false);
return postRepository.save(post);
post = postRepository.save(post);
notificationService.createNotification(post.getAuthor(), NotificationType.POST_FEATURED, post, null, null, null, null, null);
pointService.awardForFeatured(post.getAuthor().getUsername(), post.getId());
return post;
}
public Post createPost(String username,
@@ -458,6 +464,34 @@ public class PostService {
return paginate(sortByPinnedAndCreated(posts), page, pageSize);
}
public List<Post> listFeaturedPosts(List<Long> categoryIds,
List<Long> tagIds,
Integer page,
Integer pageSize) {
List<Post> posts;
boolean hasCategories = categoryIds != null && !categoryIds.isEmpty();
boolean hasTags = tagIds != null && !tagIds.isEmpty();
if (hasCategories && hasTags) {
posts = listPostsByCategoriesAndTags(categoryIds, tagIds, null, null);
} else if (hasCategories) {
posts = listPostsByCategories(categoryIds, null, null);
} else if (hasTags) {
posts = listPostsByTags(tagIds, null, null);
} else {
posts = listPosts();
}
// 仅保留 getRssExcluded 为 0 且不为空
// 若字段类型是 Boolean包装类型0 等价于 false
posts = posts.stream()
.filter(p -> p.getRssExcluded() != null && !p.getRssExcluded())
.toList();
return paginate(sortByPinnedAndCreated(posts), page, pageSize);
}
public List<Post> listPendingPosts() {
return postRepository.findByStatus(PostStatus.PENDING);
}
@@ -579,7 +613,9 @@ 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"));
if (!user.getId().equals(post.getAuthor().getId()) && user.getRole() != Role.ADMIN) {
User author = post.getAuthor();
boolean adminDeleting = !user.getId().equals(author.getId()) && user.getRole() == Role.ADMIN;
if (!user.getId().equals(author.getId()) && user.getRole() != Role.ADMIN) {
throw new IllegalArgumentException("Unauthorized");
}
for (Comment c : commentRepository.findByPostAndParentIsNullOrderByCreatedAtAsc(post)) {
@@ -596,7 +632,12 @@ public class PostService {
future.cancel(false);
}
}
String title = post.getTitle();
postRepository.delete(post);
if (adminDeleting) {
notificationService.createNotification(author, NotificationType.POST_DELETED,
null, null, null, user, null, title);
}
}
public java.util.List<Post> getPostsByIds(java.util.List<Long> ids) {