Compare commits
2 Commits
e68f1bea56
...
f28e6b7c39
| Author | SHA1 | Date | |
|---|---|---|---|
| f28e6b7c39 | |||
| 22e5041447 |
@@ -2,6 +2,6 @@
|
||||
"active": true,
|
||||
"started_at": "2026-01-26T13:01:18.447Z",
|
||||
"original_prompt": "刚刚回滚了代码,现在AI陪聊角色评论需要使用KeyboardAiCompanionCommentLikeService添加一个评论点赞接口,用来记录点赞和取消点赞。 ulw",
|
||||
"reinforcement_count": 8,
|
||||
"last_checked_at": "2026-01-27T10:35:42.226Z"
|
||||
"reinforcement_count": 10,
|
||||
"last_checked_at": "2026-01-27T11:00:42.142Z"
|
||||
}
|
||||
@@ -115,7 +115,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
"/ai-companion/page",
|
||||
"/chat/history",
|
||||
"/ai-companion/comment/add",
|
||||
"/speech/transcribe"
|
||||
"/speech/transcribe",
|
||||
"/ai-companion/comment/page"
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
|
||||
@@ -34,9 +34,10 @@ public class AiCompanionController {
|
||||
private KeyboardAiCompanionLikeService aiCompanionLikeService;
|
||||
|
||||
@PostMapping("/page")
|
||||
@Operation(summary = "分页查询AI陪聊角色", description = "分页查询已上线的AI陪聊角色列表")
|
||||
@Operation(summary = "分页查询AI陪聊角色", description = "分页查询已上线的AI陪聊角色列表,包含点赞数、评论数和当前用户点赞状态")
|
||||
public BaseResponse<IPage<AiCompanionVO>> pageList(@RequestBody PageDTO pageDTO) {
|
||||
IPage<AiCompanionVO> result = aiCompanionService.pageList(pageDTO.getPageNum(), pageDTO.getPageSize());
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
IPage<AiCompanionVO> result = aiCompanionService.pageListWithLikeStatus(userId, pageDTO.getPageNum(), pageDTO.getPageSize());
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ public class AiCompanionVO {
|
||||
@Schema(description = "评论总数")
|
||||
private Integer commentCount;
|
||||
|
||||
@Schema(description = "当前用户是否已点赞")
|
||||
private Boolean liked;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createdAt;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
@@ -45,4 +46,10 @@ public class CommentVO {
|
||||
|
||||
@Schema(description = "评论创建时间")
|
||||
private Date createdAt;
|
||||
|
||||
@Schema(description = "回复列表(仅一级评论有值,默认返回前3条)")
|
||||
private List<CommentVO> replies;
|
||||
|
||||
@Schema(description = "回复总数")
|
||||
private Integer replyCount;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,16 @@ public interface KeyboardAiCompanionService extends IService<KeyboardAiCompanion
|
||||
*/
|
||||
IPage<AiCompanionVO> pageList(Integer pageNum, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 分页查询已上线的AI陪聊角色(带当前用户点赞状态)
|
||||
*
|
||||
* @param userId 当前用户ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 分页结果
|
||||
*/
|
||||
IPage<AiCompanionVO> pageListWithLikeStatus(Long userId, Integer pageNum, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 根据AI人设ID获取系统提示词
|
||||
*
|
||||
|
||||
@@ -376,41 +376,29 @@ public class ChatServiceImpl implements ChatService {
|
||||
public ChatMessageVO message(String content, String userId, Long companionId) {
|
||||
log.info("同步对话请求, userId: {}, companionId: {}, content: {}", userId, companionId, content);
|
||||
|
||||
// ============ VIP等级检查 ============
|
||||
// ============ VIP等级检查(先检查,不增加次数) ============
|
||||
AppConfig appConfig = cfgHolder.getRef().get();
|
||||
KeyboardUser user = userService.getById(Long.parseLong(userId));
|
||||
|
||||
// 获取VIP等级(null视为0)
|
||||
int vipLevel = user != null && user.getVipLevel() != null ? user.getVipLevel() : 0;
|
||||
|
||||
// 如果VIP等级 <= 1,需要限制每日体验次数
|
||||
if (vipLevel <= 1) {
|
||||
Integer dailyLimit = appConfig.getUserRegisterProperties().getVipFreeTrialTalk();
|
||||
String redisKey = CHAT_DAILY_LIMIT_PREFIX + userId;
|
||||
// 记录是否需要扣减体验次数(VIP等级 <= 1 的用户需要扣减)
|
||||
boolean needDeductQuota = vipLevel <= 1;
|
||||
String redisKey = CHAT_DAILY_LIMIT_PREFIX + userId;
|
||||
Integer dailyLimit = appConfig.getUserRegisterProperties().getVipFreeTrialTalk();
|
||||
|
||||
// 如果VIP等级 <= 1,先检查每日体验次数是否用完
|
||||
if (needDeductQuota) {
|
||||
// 获取当前使用次数
|
||||
String countStr = stringRedisTemplate.opsForValue().get(redisKey);
|
||||
int currentCount = countStr != null ? Integer.parseInt(countStr) : 0;
|
||||
|
||||
// 检查是否超出限制
|
||||
// 检查是否超出限制,超出直接返回
|
||||
if (currentCount >= dailyLimit) {
|
||||
log.warn("用户 {} VIP等级 {} 已达到每日体验次数限制 {}", userId, vipLevel, dailyLimit);
|
||||
throw new BusinessException(ErrorCode.VIP_TRIAL_LIMIT_REACHED);
|
||||
}
|
||||
|
||||
// 增加使用次数
|
||||
Long newCount = stringRedisTemplate.opsForValue().increment(redisKey);
|
||||
|
||||
// 设置到午夜过期(只有第一次设置时需要设置过期时间)
|
||||
if (newCount != null && newCount == 1) {
|
||||
// 计算到今天午夜的剩余秒数
|
||||
LocalDateTime now = LocalDateTime.now(ZoneId.of("America/New_York"));
|
||||
LocalDateTime midnight = LocalDateTime.of(LocalDate.now(ZoneId.of("America/New_York")).plusDays(1), LocalTime.MIDNIGHT);
|
||||
long secondsUntilMidnight = ChronoUnit.SECONDS.between(now, midnight);
|
||||
stringRedisTemplate.expire(redisKey, secondsUntilMidnight, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
log.info("用户 {} VIP等级 {} 今日已使用 {}/{} 次", userId, vipLevel, newCount, dailyLimit);
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
@@ -431,6 +419,22 @@ public class ChatServiceImpl implements ChatService {
|
||||
// 保存用户消息和AI响应到聊天记录
|
||||
saveChatMessages(Long.parseLong(userId), companionId, content, response);
|
||||
|
||||
// ============ 成功后扣减体验次数 ============
|
||||
if (needDeductQuota) {
|
||||
Long newCount = stringRedisTemplate.opsForValue().increment(redisKey);
|
||||
|
||||
// 设置到午夜过期(只有第一次设置时需要设置过期时间)
|
||||
if (newCount != null && newCount == 1) {
|
||||
// 计算到今天午夜的剩余秒数
|
||||
LocalDateTime now = LocalDateTime.now(ZoneId.of("America/New_York"));
|
||||
LocalDateTime midnight = LocalDateTime.of(LocalDate.now(ZoneId.of("America/New_York")).plusDays(1), LocalTime.MIDNIGHT);
|
||||
long secondsUntilMidnight = ChronoUnit.SECONDS.between(now, midnight);
|
||||
stringRedisTemplate.expire(redisKey, secondsUntilMidnight, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
log.info("用户 {} VIP等级 {} 今日已使用 {}/{} 次", userId, vipLevel, newCount, dailyLimit);
|
||||
}
|
||||
|
||||
// 生成音频任务 ID
|
||||
String audioId = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.yolo.keyborad.service.UserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -104,12 +105,51 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
|
||||
.orderByDesc(KeyboardAiCompanionComment::getCreatedAt);
|
||||
IPage<KeyboardAiCompanionComment> entityPage = this.page(page, queryWrapper);
|
||||
|
||||
// 获取所有用户ID
|
||||
List<Long> userIds = entityPage.getRecords().stream()
|
||||
.map(KeyboardAiCompanionComment::getUserId)
|
||||
.distinct()
|
||||
// 获取所有一级评论ID
|
||||
List<Long> topCommentIds = entityPage.getRecords().stream()
|
||||
.map(KeyboardAiCompanionComment::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 批量查询回复(每条一级评论取前3条回复)
|
||||
Map<Long, List<KeyboardAiCompanionComment>> repliesMap = Map.of();
|
||||
Map<Long, Long> replyCountMap = Map.of();
|
||||
if (!topCommentIds.isEmpty()) {
|
||||
// 查询所有回复
|
||||
LambdaQueryWrapper<KeyboardAiCompanionComment> replyWrapper = new LambdaQueryWrapper<>();
|
||||
replyWrapper.in(KeyboardAiCompanionComment::getRootId, topCommentIds)
|
||||
.eq(KeyboardAiCompanionComment::getStatus, 1)
|
||||
.orderByAsc(KeyboardAiCompanionComment::getCreatedAt);
|
||||
List<KeyboardAiCompanionComment> allReplies = this.list(replyWrapper);
|
||||
|
||||
// 按rootId分组,每组取前3条
|
||||
repliesMap = allReplies.stream()
|
||||
.collect(Collectors.groupingBy(KeyboardAiCompanionComment::getRootId));
|
||||
|
||||
// 统计回复数量
|
||||
replyCountMap = repliesMap.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> (long) e.getValue().size()));
|
||||
|
||||
// 每组只保留前3条
|
||||
repliesMap = repliesMap.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().stream().limit(999).collect(Collectors.toList())
|
||||
));
|
||||
}
|
||||
|
||||
// 收集所有需要查询的用户ID(一级评论 + 回复)
|
||||
List<Long> userIds = new ArrayList<>(entityPage.getRecords().stream()
|
||||
.map(KeyboardAiCompanionComment::getUserId)
|
||||
.collect(Collectors.toSet()));
|
||||
repliesMap.values().stream()
|
||||
.flatMap(List::stream)
|
||||
.map(KeyboardAiCompanionComment::getUserId)
|
||||
.forEach(uid -> {
|
||||
if (!userIds.contains(uid)) {
|
||||
userIds.add(uid);
|
||||
}
|
||||
});
|
||||
|
||||
// 批量查询用户信息
|
||||
Map<Long, KeyboardUser> userMap = Map.of();
|
||||
if (!userIds.isEmpty()) {
|
||||
@@ -117,33 +157,55 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
|
||||
userMap = users.stream().collect(Collectors.toMap(KeyboardUser::getId, u -> u));
|
||||
}
|
||||
|
||||
// 获取当前用户已点赞的评论ID
|
||||
List<Long> commentIds = entityPage.getRecords().stream()
|
||||
// 收集所有评论ID用于查询点赞状态(一级评论 + 回复)
|
||||
List<Long> allCommentIds = new ArrayList<>(topCommentIds);
|
||||
repliesMap.values().stream()
|
||||
.flatMap(List::stream)
|
||||
.map(KeyboardAiCompanionComment::getId)
|
||||
.collect(Collectors.toList());
|
||||
Set<Long> likedCommentIds = commentLikeService.getLikedCommentIds(userId, commentIds);
|
||||
.forEach(allCommentIds::add);
|
||||
Set<Long> likedCommentIds = commentLikeService.getLikedCommentIds(userId, allCommentIds);
|
||||
|
||||
// 转换为VO
|
||||
Map<Long, KeyboardUser> finalUserMap = userMap;
|
||||
return entityPage.convert(entity -> {
|
||||
CommentVO vo = new CommentVO();
|
||||
vo.setId(entity.getId());
|
||||
vo.setCompanionId(entity.getCompanionId());
|
||||
vo.setUserId(entity.getUserId());
|
||||
vo.setParentId(entity.getParentId());
|
||||
vo.setRootId(entity.getRootId());
|
||||
vo.setContent(entity.getContent());
|
||||
vo.setLikeCount(entity.getLikeCount());
|
||||
vo.setCreatedAt(entity.getCreatedAt());
|
||||
vo.setLiked(likedCommentIds.contains(entity.getId()));
|
||||
Map<Long, List<KeyboardAiCompanionComment>> finalRepliesMap = repliesMap;
|
||||
Map<Long, Long> finalReplyCountMap = replyCountMap;
|
||||
|
||||
return entityPage.convert(entity -> {
|
||||
CommentVO vo = convertToVO(entity, finalUserMap, likedCommentIds);
|
||||
|
||||
// 填充回复列表
|
||||
List<KeyboardAiCompanionComment> replies = finalRepliesMap.getOrDefault(entity.getId(), List.of());
|
||||
List<CommentVO> replyVOs = replies.stream()
|
||||
.map(reply -> convertToVO(reply, finalUserMap, likedCommentIds))
|
||||
.collect(Collectors.toList());
|
||||
vo.setReplies(replyVOs);
|
||||
vo.setReplyCount(finalReplyCountMap.getOrDefault(entity.getId(), 0L).intValue());
|
||||
|
||||
// 填充用户信息
|
||||
KeyboardUser user = finalUserMap.get(entity.getUserId());
|
||||
if (user != null) {
|
||||
vo.setUserName(user.getNickName());
|
||||
vo.setUserAvatar(user.getAvatarUrl());
|
||||
}
|
||||
return vo;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将评论实体转换为VO
|
||||
*/
|
||||
private CommentVO convertToVO(KeyboardAiCompanionComment entity, Map<Long, KeyboardUser> userMap, Set<Long> likedCommentIds) {
|
||||
CommentVO vo = new CommentVO();
|
||||
vo.setId(entity.getId());
|
||||
vo.setCompanionId(entity.getCompanionId());
|
||||
vo.setUserId(entity.getUserId());
|
||||
vo.setParentId(entity.getParentId());
|
||||
vo.setRootId(entity.getRootId());
|
||||
vo.setContent(entity.getContent());
|
||||
vo.setLikeCount(entity.getLikeCount());
|
||||
vo.setCreatedAt(entity.getCreatedAt());
|
||||
vo.setLiked(likedCommentIds.contains(entity.getId()));
|
||||
|
||||
// 填充用户信息
|
||||
KeyboardUser user = userMap.get(entity.getUserId());
|
||||
if (user != null) {
|
||||
vo.setUserName(user.getNickName());
|
||||
vo.setUserAvatar(user.getAvatarUrl());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.yolo.keyborad.service.KeyboardAiCompanionService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/*
|
||||
@@ -83,6 +84,58 @@ public class KeyboardAiCompanionServiceImpl extends ServiceImpl<KeyboardAiCompan
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<AiCompanionVO> pageListWithLikeStatus(Long userId, Integer pageNum, Integer pageSize) {
|
||||
Page<KeyboardAiCompanion> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<KeyboardAiCompanion> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(KeyboardAiCompanion::getStatus, 1)
|
||||
.eq(KeyboardAiCompanion::getVisibility, 1)
|
||||
.orderByDesc(KeyboardAiCompanion::getSortOrder)
|
||||
.orderByDesc(KeyboardAiCompanion::getPopularityScore);
|
||||
IPage<KeyboardAiCompanion> entityPage = this.page(page, queryWrapper);
|
||||
|
||||
// 获取所有角色ID
|
||||
List<Long> companionIds = entityPage.getRecords().stream()
|
||||
.map(KeyboardAiCompanion::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 批量统计点赞数
|
||||
Map<Long, Long> likeCountMap = Map.of();
|
||||
if (!companionIds.isEmpty()) {
|
||||
LambdaQueryWrapper<KeyboardAiCompanionLike> likeWrapper = new LambdaQueryWrapper<>();
|
||||
likeWrapper.in(KeyboardAiCompanionLike::getCompanionId, companionIds)
|
||||
.eq(KeyboardAiCompanionLike::getStatus, (short) 1);
|
||||
List<KeyboardAiCompanionLike> likes = companionLikeService.list(likeWrapper);
|
||||
likeCountMap = likes.stream()
|
||||
.collect(Collectors.groupingBy(KeyboardAiCompanionLike::getCompanionId, Collectors.counting()));
|
||||
}
|
||||
|
||||
// 批量统计评论数
|
||||
Map<Long, Long> commentCountMap = Map.of();
|
||||
if (!companionIds.isEmpty()) {
|
||||
LambdaQueryWrapper<KeyboardAiCompanionComment> commentWrapper = new LambdaQueryWrapper<>();
|
||||
commentWrapper.in(KeyboardAiCompanionComment::getCompanionId, companionIds)
|
||||
.eq(KeyboardAiCompanionComment::getStatus, (short) 1);
|
||||
List<KeyboardAiCompanionComment> comments = companionCommentService.list(commentWrapper);
|
||||
commentCountMap = comments.stream()
|
||||
.collect(Collectors.groupingBy(KeyboardAiCompanionComment::getCompanionId, Collectors.counting()));
|
||||
}
|
||||
|
||||
// 获取当前用户已点赞的角色ID
|
||||
Set<Long> likedCompanionIds = companionLikeService.getLikedCompanionIds(userId, companionIds);
|
||||
|
||||
// 转换为VO并填充统计数据和点赞状态
|
||||
Map<Long, Long> finalLikeCountMap = likeCountMap;
|
||||
Map<Long, Long> finalCommentCountMap = commentCountMap;
|
||||
return entityPage.convert(entity -> {
|
||||
AiCompanionVO vo = BeanUtil.copyProperties(entity, AiCompanionVO.class);
|
||||
vo.setLikeCount(finalLikeCountMap.getOrDefault(entity.getId(), 0L).intValue());
|
||||
vo.setCommentCount(finalCommentCountMap.getOrDefault(entity.getId(), 0L).intValue());
|
||||
vo.setLiked(likedCompanionIds.contains(entity.getId()));
|
||||
return vo;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSystemPromptById(Long companionId) {
|
||||
KeyboardAiCompanion companion = this.getById(companionId);
|
||||
|
||||
Reference in New Issue
Block a user