feat(ai-companion): 新增评论点赞功能及点赞状态查询
This commit is contained in:
7
.omc/ultrawork-state.json
Normal file
7
.omc/ultrawork-state.json
Normal file
@@ -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"
|
||||
}
|
||||
@@ -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, "令牌已过期"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Long> addComment(@RequestBody CommentAddReq req) {
|
||||
@@ -47,14 +52,27 @@ public class AiCompanionCommentController {
|
||||
}
|
||||
|
||||
@PostMapping("/page")
|
||||
@Operation(summary = "分页查询评论", description = "分页查询AI陪聊角色的评论列表")
|
||||
@Operation(summary = "分页查询评论", description = "分页查询AI陪聊角色的评论列表,包含当前用户是否已点赞状态")
|
||||
public BaseResponse<IPage<CommentVO>> pageComments(@RequestBody CommentPageReq req) {
|
||||
if (req.getCompanionId() == null) {
|
||||
throw new BusinessException(ErrorCode.COMPANION_ID_EMPTY);
|
||||
}
|
||||
|
||||
IPage<CommentVO> result = commentService.pageComments(req.getCompanionId(),
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
IPage<CommentVO> result = commentService.pageCommentsWithLikeStatus(userId, req.getCompanionId(),
|
||||
req.getPageNum(), req.getPageSize());
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/like")
|
||||
@Operation(summary = "点赞/取消点赞", description = "对评论进行点赞或取消点赞操作,返回true表示点赞成功,false表示取消点赞成功")
|
||||
public BaseResponse<Boolean> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<KeyboardAiCompanionCommentLike> {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -40,6 +40,9 @@ public class CommentVO {
|
||||
@Schema(description = "点赞数")
|
||||
private Integer likeCount;
|
||||
|
||||
@Schema(description = "当前用户是否已点赞")
|
||||
private Boolean liked;
|
||||
|
||||
@Schema(description = "评论创建时间")
|
||||
private Date createdAt;
|
||||
}
|
||||
|
||||
@@ -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<KeyboardAiCompanionCommentLike> {
|
||||
|
||||
/**
|
||||
* 检查用户是否已点赞该评论
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param commentId 评论ID
|
||||
* @return 是否已点赞
|
||||
*/
|
||||
boolean hasLiked(Long userId, Long commentId);
|
||||
|
||||
/**
|
||||
* 批量检查用户是否已点赞评论
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param commentIds 评论ID列表
|
||||
* @return 已点赞的评论ID集合
|
||||
*/
|
||||
Set<Long> getLikedCommentIds(Long userId, List<Long> commentIds);
|
||||
|
||||
/**
|
||||
* 点赞或取消点赞评论
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param commentId 评论ID
|
||||
* @return true=点赞成功,false=取消点赞成功
|
||||
*/
|
||||
boolean toggleLike(Long userId, Long commentId);
|
||||
}
|
||||
@@ -32,4 +32,15 @@ public interface KeyboardAiCompanionCommentService extends IService<KeyboardAiCo
|
||||
* @return 分页结果
|
||||
*/
|
||||
IPage<CommentVO> pageComments(Long companionId, Integer pageNum, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 分页查询评论(带用户点赞状态)
|
||||
*
|
||||
* @param userId 当前用户ID
|
||||
* @param companionId AI陪伴角色ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 分页结果
|
||||
*/
|
||||
IPage<CommentVO> pageCommentsWithLikeStatus(Long userId, Long companionId, Integer pageNum, Integer pageSize);
|
||||
}
|
||||
|
||||
@@ -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<KeyboardAiCompanionCommentLikeMapper, KeyboardAiCompanionCommentLike> implements KeyboardAiCompanionCommentLikeService {
|
||||
|
||||
@Resource
|
||||
private KeyboardAiCompanionCommentMapper commentMapper;
|
||||
|
||||
@Override
|
||||
public boolean hasLiked(Long userId, Long commentId) {
|
||||
LambdaQueryWrapper<KeyboardAiCompanionCommentLike> 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<Long> getLikedCommentIds(Long userId, List<Long> commentIds) {
|
||||
if (commentIds == null || commentIds.isEmpty()) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
LambdaQueryWrapper<KeyboardAiCompanionCommentLike> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(KeyboardAiCompanionCommentLike::getUserId, userId)
|
||||
.in(KeyboardAiCompanionCommentLike::getCommentId, commentIds)
|
||||
.eq(KeyboardAiCompanionCommentLike::getStatus, (short) 1);
|
||||
List<KeyboardAiCompanionCommentLike> 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<KeyboardAiCompanionCommentLike> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<KeyboardA
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
@Resource
|
||||
private KeyboardAiCompanionCommentLikeService commentLikeService;
|
||||
|
||||
@Override
|
||||
public Long addComment(Long userId, Long companionId, String content, Long parentId, Long rootId) {
|
||||
KeyboardAiCompanionComment comment = new KeyboardAiCompanionComment();
|
||||
@@ -88,4 +93,57 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
|
||||
return vo;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<CommentVO> pageCommentsWithLikeStatus(Long userId, Long companionId, Integer pageNum, Integer pageSize) {
|
||||
Page<KeyboardAiCompanionComment> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<KeyboardAiCompanionComment> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(KeyboardAiCompanionComment::getCompanionId, companionId)
|
||||
.eq(KeyboardAiCompanionComment::getStatus, 1)
|
||||
.isNull(KeyboardAiCompanionComment::getParentId)
|
||||
.orderByDesc(KeyboardAiCompanionComment::getCreatedAt);
|
||||
IPage<KeyboardAiCompanionComment> entityPage = this.page(page, queryWrapper);
|
||||
|
||||
// 获取所有用户ID
|
||||
List<Long> userIds = entityPage.getRecords().stream()
|
||||
.map(KeyboardAiCompanionComment::getUserId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 批量查询用户信息
|
||||
Map<Long, KeyboardUser> userMap = Map.of();
|
||||
if (!userIds.isEmpty()) {
|
||||
List<KeyboardUser> users = userService.listByIds(userIds);
|
||||
userMap = users.stream().collect(Collectors.toMap(KeyboardUser::getId, u -> u));
|
||||
}
|
||||
|
||||
// 获取当前用户已点赞的评论ID
|
||||
List<Long> commentIds = entityPage.getRecords().stream()
|
||||
.map(KeyboardAiCompanionComment::getId)
|
||||
.collect(Collectors.toList());
|
||||
Set<Long> likedCommentIds = commentLikeService.getLikedCommentIds(userId, commentIds);
|
||||
|
||||
// 转换为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()));
|
||||
|
||||
// 填充用户信息
|
||||
KeyboardUser user = finalUserMap.get(entity.getUserId());
|
||||
if (user != null) {
|
||||
vo.setUserName(user.getNickName());
|
||||
vo.setUserAvatar(user.getAvatarUrl());
|
||||
}
|
||||
return vo;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yolo.keyborad.mapper.KeyboardAiCompanionCommentLikeMapper">
|
||||
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardAiCompanionCommentLike">
|
||||
<!--@mbg.generated-->
|
||||
<!--@Table keyboard_ai_companion_comment_like-->
|
||||
<id column="id" jdbcType="BIGINT" property="id" />
|
||||
<result column="comment_id" jdbcType="BIGINT" property="commentId" />
|
||||
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||
<result column="status" jdbcType="SMALLINT" property="status" />
|
||||
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
|
||||
</resultMap>
|
||||
<sql id="Base_Column_List">
|
||||
<!--@mbg.generated-->
|
||||
id, comment_id, user_id, "status", created_at, updated_at
|
||||
</sql>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user