diff --git a/.omc/ultrawork-state.json b/.omc/ultrawork-state.json new file mode 100644 index 0000000..2bd7f53 --- /dev/null +++ b/.omc/ultrawork-state.json @@ -0,0 +1,7 @@ +{ + "active": true, + "started_at": "2026-01-26T13:01:18.447Z", + "original_prompt": "刚刚回滚了代码,现在AI陪聊角色评论需要使用KeyboardAiCompanionCommentLikeService添加一个评论点赞接口,用来记录点赞和取消点赞。 ulw", + "reinforcement_count": 2, + "last_checked_at": "2026-01-26T13:21:19.022Z" +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/common/ErrorCode.java b/src/main/java/com/yolo/keyborad/common/ErrorCode.java index 153d547..7fb4c55 100644 --- a/src/main/java/com/yolo/keyborad/common/ErrorCode.java +++ b/src/main/java/com/yolo/keyborad/common/ErrorCode.java @@ -32,6 +32,7 @@ public enum ErrorCode { COMPANION_ID_EMPTY(40012, "AI陪聊角色ID不能为空"), COMMENT_CONTENT_EMPTY(40013, "评论内容不能为空"), COMMENT_NOT_FOUND(40014, "评论不存在"), + COMMENT_ID_EMPTY(40015, "评论ID不能为空"), TOKEN_NOT_FOUND(40102, "未能读取到有效用户令牌"), TOKEN_INVALID(40103, "令牌无效"), TOKEN_TIMEOUT(40104, "令牌已过期"), diff --git a/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java b/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java index 7f69056..26d5837 100644 --- a/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java +++ b/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java @@ -113,7 +113,8 @@ public class SaTokenConfigure implements WebMvcConfigurer { "/chat/voice", "/chat/audio/*", "/ai-companion/page", - "/chat/history" + "/chat/history", + "/ai-companion/comment/add" }; } @Bean diff --git a/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentController.java b/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentController.java index 2e75ed0..5b7aae2 100644 --- a/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentController.java +++ b/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentController.java @@ -8,9 +8,11 @@ import com.yolo.keyborad.common.ErrorCode; import com.yolo.keyborad.common.ResultUtils; import com.yolo.keyborad.exception.BusinessException; import com.yolo.keyborad.model.dto.comment.CommentAddReq; +import com.yolo.keyborad.model.dto.comment.CommentLikeReq; import com.yolo.keyborad.model.dto.comment.CommentPageReq; import com.yolo.keyborad.model.vo.CommentVO; import com.yolo.keyborad.service.KeyboardAiCompanionCommentService; +import com.yolo.keyborad.service.KeyboardAiCompanionCommentLikeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; @@ -30,6 +32,9 @@ public class AiCompanionCommentController { @Resource private KeyboardAiCompanionCommentService commentService; + @Resource + private KeyboardAiCompanionCommentLikeService commentLikeService; + @PostMapping("/add") @Operation(summary = "发表评论", description = "用户对AI陪聊角色发表评论") public BaseResponse addComment(@RequestBody CommentAddReq req) { @@ -47,14 +52,27 @@ public class AiCompanionCommentController { } @PostMapping("/page") - @Operation(summary = "分页查询评论", description = "分页查询AI陪聊角色的评论列表") + @Operation(summary = "分页查询评论", description = "分页查询AI陪聊角色的评论列表,包含当前用户是否已点赞状态") public BaseResponse> pageComments(@RequestBody CommentPageReq req) { if (req.getCompanionId() == null) { throw new BusinessException(ErrorCode.COMPANION_ID_EMPTY); } - IPage result = commentService.pageComments(req.getCompanionId(), + Long userId = StpUtil.getLoginIdAsLong(); + IPage result = commentService.pageCommentsWithLikeStatus(userId, req.getCompanionId(), req.getPageNum(), req.getPageSize()); return ResultUtils.success(result); } + + @PostMapping("/like") + @Operation(summary = "点赞/取消点赞", description = "对评论进行点赞或取消点赞操作,返回true表示点赞成功,false表示取消点赞成功") + public BaseResponse toggleLike(@RequestBody CommentLikeReq req) { + if (req.getCommentId() == null) { + throw new BusinessException(ErrorCode.COMMENT_ID_EMPTY); + } + + Long userId = StpUtil.getLoginIdAsLong(); + boolean result = commentLikeService.toggleLike(userId, req.getCommentId()); + return ResultUtils.success(result); + } } diff --git a/src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionCommentLikeMapper.java b/src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionCommentLikeMapper.java new file mode 100644 index 0000000..62344ea --- /dev/null +++ b/src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionCommentLikeMapper.java @@ -0,0 +1,12 @@ +package com.yolo.keyborad.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionCommentLike; + +/* + * @author: ziin + * @date: 2026/1/26 20:57 + */ + +public interface KeyboardAiCompanionCommentLikeMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/model/dto/comment/CommentLikeReq.java b/src/main/java/com/yolo/keyborad/model/dto/comment/CommentLikeReq.java new file mode 100644 index 0000000..6b29121 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/comment/CommentLikeReq.java @@ -0,0 +1,16 @@ +package com.yolo.keyborad.model.dto.comment; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/* + * @author: ziin + * @date: 2026/1/26 + */ +@Data +@Schema(description = "评论点赞请求") +public class CommentLikeReq { + + @Schema(description = "评论ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long commentId; +} diff --git a/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionCommentLike.java b/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionCommentLike.java new file mode 100644 index 0000000..949a10f --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionCommentLike.java @@ -0,0 +1,64 @@ +package com.yolo.keyborad.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Date; +import lombok.Data; + +/* +* @author: ziin +* @date: 2026/1/26 20:57 +*/ + +/** + * 用户对AI陪伴角色评论的点赞记录表,用于记录点赞与取消点赞行为 + */ +@Schema(description = "用户对AI陪伴角色评论的点赞记录表,用于记录点赞与取消点赞行为") +@Data +@TableName(value = "keyboard_ai_companion_comment_like") +public class KeyboardAiCompanionCommentLike { + /** + * 评论点赞记录唯一ID + */ + @TableId(value = "id", type = IdType.AUTO) + @Schema(description = "评论点赞记录唯一ID") + private Long id; + + /** + * 被点赞的评论ID + */ + @TableField(value = "comment_id") + @Schema(description = "被点赞的评论ID") + private Long commentId; + + /** + * 点赞用户ID + */ + @TableField(value = "user_id") + @Schema(description = "点赞用户ID") + private Long userId; + + /** + * 点赞状态:1=已点赞,0=已取消 + */ + @TableField(value = "\"status\"") + @Schema(description = "点赞状态:1=已点赞,0=已取消") + private Short status; + + /** + * 点赞记录创建时间 + */ + @TableField(value = "created_at") + @Schema(description = "点赞记录创建时间") + private Date createdAt; + + /** + * 点赞状态更新时间 + */ + @TableField(value = "updated_at") + @Schema(description = "点赞状态更新时间") + private Date updatedAt; +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/model/vo/CommentVO.java b/src/main/java/com/yolo/keyborad/model/vo/CommentVO.java index ddcc961..c67db8f 100644 --- a/src/main/java/com/yolo/keyborad/model/vo/CommentVO.java +++ b/src/main/java/com/yolo/keyborad/model/vo/CommentVO.java @@ -40,6 +40,9 @@ public class CommentVO { @Schema(description = "点赞数") private Integer likeCount; + @Schema(description = "当前用户是否已点赞") + private Boolean liked; + @Schema(description = "评论创建时间") private Date createdAt; } diff --git a/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentLikeService.java b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentLikeService.java new file mode 100644 index 0000000..1a2ee61 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentLikeService.java @@ -0,0 +1,41 @@ +package com.yolo.keyborad.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionCommentLike; + +import java.util.List; +import java.util.Set; + +/* + * @author: ziin + * @date: 2026/1/26 + */ +public interface KeyboardAiCompanionCommentLikeService extends IService { + + /** + * 检查用户是否已点赞该评论 + * + * @param userId 用户ID + * @param commentId 评论ID + * @return 是否已点赞 + */ + boolean hasLiked(Long userId, Long commentId); + + /** + * 批量检查用户是否已点赞评论 + * + * @param userId 用户ID + * @param commentIds 评论ID列表 + * @return 已点赞的评论ID集合 + */ + Set getLikedCommentIds(Long userId, List commentIds); + + /** + * 点赞或取消点赞评论 + * + * @param userId 用户ID + * @param commentId 评论ID + * @return true=点赞成功,false=取消点赞成功 + */ + boolean toggleLike(Long userId, Long commentId); +} diff --git a/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentService.java b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentService.java index 2b3fab1..ccefeef 100644 --- a/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentService.java +++ b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionCommentService.java @@ -32,4 +32,15 @@ public interface KeyboardAiCompanionCommentService extends IService pageComments(Long companionId, Integer pageNum, Integer pageSize); + + /** + * 分页查询评论(带用户点赞状态) + * + * @param userId 当前用户ID + * @param companionId AI陪伴角色ID + * @param pageNum 页码 + * @param pageSize 每页数量 + * @return 分页结果 + */ + IPage pageCommentsWithLikeStatus(Long userId, Long companionId, Integer pageNum, Integer pageSize); } diff --git a/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentLikeServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentLikeServiceImpl.java new file mode 100644 index 0000000..b7adb38 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentLikeServiceImpl.java @@ -0,0 +1,111 @@ +package com.yolo.keyborad.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yolo.keyborad.common.ErrorCode; +import com.yolo.keyborad.exception.BusinessException; +import com.yolo.keyborad.mapper.KeyboardAiCompanionCommentLikeMapper; +import com.yolo.keyborad.mapper.KeyboardAiCompanionCommentMapper; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionComment; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionCommentLike; +import com.yolo.keyborad.service.KeyboardAiCompanionCommentLikeService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/* + * @author: ziin + * @date: 2026/1/26 + */ +@Service +public class KeyboardAiCompanionCommentLikeServiceImpl extends ServiceImpl implements KeyboardAiCompanionCommentLikeService { + + @Resource + private KeyboardAiCompanionCommentMapper commentMapper; + + @Override + public boolean hasLiked(Long userId, Long commentId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(KeyboardAiCompanionCommentLike::getUserId, userId) + .eq(KeyboardAiCompanionCommentLike::getCommentId, commentId) + .eq(KeyboardAiCompanionCommentLike::getStatus, (short) 1); + return this.count(queryWrapper) > 0; + } + + @Override + public Set getLikedCommentIds(Long userId, List commentIds) { + if (commentIds == null || commentIds.isEmpty()) { + return new HashSet<>(); + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(KeyboardAiCompanionCommentLike::getUserId, userId) + .in(KeyboardAiCompanionCommentLike::getCommentId, commentIds) + .eq(KeyboardAiCompanionCommentLike::getStatus, (short) 1); + List likes = this.list(queryWrapper); + return likes.stream() + .map(KeyboardAiCompanionCommentLike::getCommentId) + .collect(Collectors.toSet()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean toggleLike(Long userId, Long commentId) { + // 检查评论是否存在 + KeyboardAiCompanionComment comment = commentMapper.selectById(commentId); + if (comment == null || comment.getStatus() != 1) { + throw new BusinessException(ErrorCode.COMMENT_NOT_FOUND); + } + + // 查找现有点赞记录 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(KeyboardAiCompanionCommentLike::getUserId, userId) + .eq(KeyboardAiCompanionCommentLike::getCommentId, commentId); + KeyboardAiCompanionCommentLike existingLike = this.getOne(queryWrapper); + + Date now = new Date(); + boolean isLiked; + + if (existingLike != null) { + // 切换点赞状态 + if (existingLike.getStatus() == 1) { + // 取消点赞 + existingLike.setStatus((short) 0); + existingLike.setUpdatedAt(now); + this.updateById(existingLike); + // 减少点赞数 + comment.setLikeCount(Math.max(0, comment.getLikeCount() - 1)); + isLiked = false; + } else { + // 重新点赞 + existingLike.setStatus((short) 1); + existingLike.setUpdatedAt(now); + this.updateById(existingLike); + // 增加点赞数 + comment.setLikeCount(comment.getLikeCount() + 1); + isLiked = true; + } + } else { + // 新增点赞记录 + KeyboardAiCompanionCommentLike like = new KeyboardAiCompanionCommentLike(); + like.setUserId(userId); + like.setCommentId(commentId); + like.setStatus((short) 1); + like.setCreatedAt(now); + like.setUpdatedAt(now); + this.save(like); + // 增加点赞数 + comment.setLikeCount(comment.getLikeCount() + 1); + isLiked = true; + } + + // 更新评论点赞数 + commentMapper.updateById(comment); + return isLiked; + } +} diff --git a/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentServiceImpl.java index 073ce22..f7d173e 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionCommentServiceImpl.java @@ -9,6 +9,7 @@ import com.yolo.keyborad.model.entity.KeyboardAiCompanionComment; import com.yolo.keyborad.model.entity.KeyboardUser; import com.yolo.keyborad.model.vo.CommentVO; import com.yolo.keyborad.service.KeyboardAiCompanionCommentService; +import com.yolo.keyborad.service.KeyboardAiCompanionCommentLikeService; import com.yolo.keyborad.service.UserService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -16,6 +17,7 @@ import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /* @@ -28,6 +30,9 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl pageCommentsWithLikeStatus(Long userId, Long companionId, Integer pageNum, Integer pageSize) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(KeyboardAiCompanionComment::getCompanionId, companionId) + .eq(KeyboardAiCompanionComment::getStatus, 1) + .isNull(KeyboardAiCompanionComment::getParentId) + .orderByDesc(KeyboardAiCompanionComment::getCreatedAt); + IPage entityPage = this.page(page, queryWrapper); + + // 获取所有用户ID + List userIds = entityPage.getRecords().stream() + .map(KeyboardAiCompanionComment::getUserId) + .distinct() + .collect(Collectors.toList()); + + // 批量查询用户信息 + Map userMap = Map.of(); + if (!userIds.isEmpty()) { + List users = userService.listByIds(userIds); + userMap = users.stream().collect(Collectors.toMap(KeyboardUser::getId, u -> u)); + } + + // 获取当前用户已点赞的评论ID + List commentIds = entityPage.getRecords().stream() + .map(KeyboardAiCompanionComment::getId) + .collect(Collectors.toList()); + Set likedCommentIds = commentLikeService.getLikedCommentIds(userId, commentIds); + + // 转换为VO + Map 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())); + + // 填充用户信息 + KeyboardUser user = finalUserMap.get(entity.getUserId()); + if (user != null) { + vo.setUserName(user.getNickName()); + vo.setUserAvatar(user.getAvatarUrl()); + } + return vo; + }); + } } diff --git a/src/main/resources/mapper/KeyboardAiCompanionCommentLikeMapper.xml b/src/main/resources/mapper/KeyboardAiCompanionCommentLikeMapper.xml new file mode 100644 index 0000000..3d87e26 --- /dev/null +++ b/src/main/resources/mapper/KeyboardAiCompanionCommentLikeMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + id, comment_id, user_id, "status", created_at, updated_at + + \ No newline at end of file