From 1fa24f7e343e43e3adc590dec259b9eb82f83434 Mon Sep 17 00:00:00 2001 From: ziin Date: Mon, 23 Mar 2026 11:08:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(comment):=20=E6=96=B0=E5=A2=9E=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=B1=8F=E8=94=BD=E5=85=B3=E7=B3=BB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交完整实现了评论屏蔽关系模块,包括: - 屏蔽关系实体 KeyboardCommentBlockRelation - 屏蔽请求 DTO CommentBlockReq - 屏蔽用户 VO CommentBlockedUserVO - 控制器、服务层及 MyBatis 映射文件 --- .../AiCompanionCommentBlockController.java | 65 ++++++ .../KeyboardCommentBlockRelationMapper.java | 12 ++ .../model/dto/comment/CommentBlockReq.java | 16 ++ .../entity/KeyboardCommentBlockRelation.java | 92 +++++++++ .../model/vo/CommentBlockedUserVO.java | 30 +++ .../KeyboardCommentBlockRelationService.java | 21 ++ ...yboardCommentBlockRelationServiceImpl.java | 187 ++++++++++++++++++ .../KeyboardCommentBlockRelationMapper.xml | 23 +++ 8 files changed, 446 insertions(+) create mode 100644 src/main/java/com/yolo/keyborad/controller/AiCompanionCommentBlockController.java create mode 100644 src/main/java/com/yolo/keyborad/mapper/KeyboardCommentBlockRelationMapper.java create mode 100644 src/main/java/com/yolo/keyborad/model/dto/comment/CommentBlockReq.java create mode 100644 src/main/java/com/yolo/keyborad/model/entity/KeyboardCommentBlockRelation.java create mode 100644 src/main/java/com/yolo/keyborad/model/vo/CommentBlockedUserVO.java create mode 100644 src/main/java/com/yolo/keyborad/service/KeyboardCommentBlockRelationService.java create mode 100644 src/main/java/com/yolo/keyborad/service/impl/KeyboardCommentBlockRelationServiceImpl.java create mode 100644 src/main/resources/mapper/KeyboardCommentBlockRelationMapper.xml diff --git a/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentBlockController.java b/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentBlockController.java new file mode 100644 index 0000000..0e16932 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/controller/AiCompanionCommentBlockController.java @@ -0,0 +1,65 @@ +package com.yolo.keyborad.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.yolo.keyborad.common.BaseResponse; +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.CommentBlockReq; +import com.yolo.keyborad.model.vo.CommentBlockedUserVO; +import com.yolo.keyborad.service.KeyboardCommentBlockRelationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/* + * @author: ziin + * @date: 2026/3/23 + */ +@RestController +@RequestMapping("/ai-companion/comment/block") +@Tag(name = "AI陪聊角色评论拉黑", description = "AI陪聊角色评论区拉黑管理接口") +public class AiCompanionCommentBlockController { + + @Resource + private KeyboardCommentBlockRelationService commentBlockRelationService; + + @PostMapping("/add") + @Operation(summary = "拉黑评论用户", description = "在评论区拉黑指定用户") + public BaseResponse blockUser(@RequestBody CommentBlockReq req) { + Long blockedUserId = validateBlockedUserId(req); + Long blockerUserId = StpUtil.getLoginIdAsLong(); + commentBlockRelationService.blockUser(blockerUserId, blockedUserId); + return ResultUtils.success(true); + } + + @PostMapping("/cancel") + @Operation(summary = "取消拉黑评论用户", description = "取消在评论区对指定用户的拉黑") + public BaseResponse unblockUser(@RequestBody CommentBlockReq req) { + Long blockedUserId = validateBlockedUserId(req); + Long blockerUserId = StpUtil.getLoginIdAsLong(); + commentBlockRelationService.unblockUser(blockerUserId, blockedUserId); + return ResultUtils.success(true); + } + + @GetMapping("/list") + @Operation(summary = "查询已拉黑用户列表", description = "查询当前登录用户在评论区已拉黑的用户列表") + public BaseResponse> listBlockedUsers() { + Long blockerUserId = StpUtil.getLoginIdAsLong(); + return ResultUtils.success(commentBlockRelationService.listBlockedUsers(blockerUserId)); + } + + private Long validateBlockedUserId(CommentBlockReq req) { + if (req == null || req.getBlockedUserId() == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "被拉黑用户ID不能为空"); + } + return req.getBlockedUserId(); + } +} diff --git a/src/main/java/com/yolo/keyborad/mapper/KeyboardCommentBlockRelationMapper.java b/src/main/java/com/yolo/keyborad/mapper/KeyboardCommentBlockRelationMapper.java new file mode 100644 index 0000000..f34ac4b --- /dev/null +++ b/src/main/java/com/yolo/keyborad/mapper/KeyboardCommentBlockRelationMapper.java @@ -0,0 +1,12 @@ +package com.yolo.keyborad.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yolo.keyborad.model.entity.KeyboardCommentBlockRelation; + +/* +* @author: ziin +* @date: 2026/3/23 10:30 +*/ + +public interface KeyboardCommentBlockRelationMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/model/dto/comment/CommentBlockReq.java b/src/main/java/com/yolo/keyborad/model/dto/comment/CommentBlockReq.java new file mode 100644 index 0000000..410fcb1 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/comment/CommentBlockReq.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/3/23 + */ +@Data +@Schema(description = "评论区拉黑请求") +public class CommentBlockReq { + + @Schema(description = "被拉黑用户ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long blockedUserId; +} diff --git a/src/main/java/com/yolo/keyborad/model/entity/KeyboardCommentBlockRelation.java b/src/main/java/com/yolo/keyborad/model/entity/KeyboardCommentBlockRelation.java new file mode 100644 index 0000000..3327ba9 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/entity/KeyboardCommentBlockRelation.java @@ -0,0 +1,92 @@ +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/3/23 10:30 +*/ + +/** + * 评论区拉黑关系表 + */ +@Schema(description="评论区拉黑关系表") +@Data +@TableName(value = "keyboard_comment_block_relation") +public class KeyboardCommentBlockRelation { + /** + * 主键 + */ + @TableId(value = "id", type = IdType.AUTO) + @Schema(description="主键") + private Long id; + + /** + * 拉黑发起人 + */ + @TableField(value = "blocker_user_id") + @Schema(description="拉黑发起人") + private Long blockerUserId; + + /** + * 被拉黑用户 + */ + @TableField(value = "blocked_user_id") + @Schema(description="被拉黑用户") + private Long blockedUserId; + + /** + * 作用域类型: 1评论区全局 2帖子 3圈子 4直播间 + */ + @TableField(value = "scope_type") + @Schema(description="作用域类型: 1评论区全局 2帖子 3圈子 4直播间") + private Short scopeType; + + /** + * 作用域ID, 全局为0 + */ + @TableField(value = "scope_id") + @Schema(description="作用域ID, 全局为0") + private Long scopeId; + + /** + * 状态: 1有效 0取消 + */ + @TableField(value = "\"status\"") + @Schema(description="状态: 1有效 0取消") + private Short status; + + /** + * 来源: 1用户操作 2风控 3管理后台 + */ + @TableField(value = "\"source\"") + @Schema(description="来源: 1用户操作 2风控 3管理后台") + private Short source; + + /** + * 创建时间 + */ + @TableField(value = "created_at") + @Schema(description="创建时间") + private Date createdAt; + + /** + * 更新时间 + */ + @TableField(value = "updated_at") + @Schema(description="更新时间") + private Date updatedAt; + + /** + * 删除时间 + */ + @TableField(value = "deleted_at") + @Schema(description="删除时间") + private Date deletedAt; +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/model/vo/CommentBlockedUserVO.java b/src/main/java/com/yolo/keyborad/model/vo/CommentBlockedUserVO.java new file mode 100644 index 0000000..1e49a2c --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/vo/CommentBlockedUserVO.java @@ -0,0 +1,30 @@ +package com.yolo.keyborad.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/* + * @author: ziin + * @date: 2026/3/23 + */ +@Data +@Schema(description = "评论区已拉黑用户") +public class CommentBlockedUserVO { + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户UID") + private Long userUid; + + @Schema(description = "用户昵称") + private String userName; + + @Schema(description = "用户头像") + private String userAvatar; + + @Schema(description = "拉黑时间") + private Date blockedAt; +} diff --git a/src/main/java/com/yolo/keyborad/service/KeyboardCommentBlockRelationService.java b/src/main/java/com/yolo/keyborad/service/KeyboardCommentBlockRelationService.java new file mode 100644 index 0000000..1210394 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/KeyboardCommentBlockRelationService.java @@ -0,0 +1,21 @@ +package com.yolo.keyborad.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yolo.keyborad.model.entity.KeyboardCommentBlockRelation; +import com.yolo.keyborad.model.vo.CommentBlockedUserVO; + +import java.util.List; + +/* +* @author: ziin +* @date: 2026/3/23 10:30 +*/ + +public interface KeyboardCommentBlockRelationService extends IService { + + void blockUser(Long blockerUserId, Long blockedUserId); + + void unblockUser(Long blockerUserId, Long blockedUserId); + + List listBlockedUsers(Long blockerUserId); +} diff --git a/src/main/java/com/yolo/keyborad/service/impl/KeyboardCommentBlockRelationServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/KeyboardCommentBlockRelationServiceImpl.java new file mode 100644 index 0000000..bfc7f38 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/impl/KeyboardCommentBlockRelationServiceImpl.java @@ -0,0 +1,187 @@ +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.KeyboardCommentBlockRelationMapper; +import com.yolo.keyborad.model.entity.KeyboardCommentBlockRelation; +import com.yolo.keyborad.model.entity.KeyboardUser; +import com.yolo.keyborad.model.vo.CommentBlockedUserVO; +import com.yolo.keyborad.service.KeyboardCommentBlockRelationService; +import com.yolo.keyborad.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +/* +* @author: ziin +* @date: 2026/3/23 10:30 +*/ + +@Service +public class KeyboardCommentBlockRelationServiceImpl extends ServiceImpl implements KeyboardCommentBlockRelationService { + + private static final short GLOBAL_SCOPE_TYPE = 1; + + private static final long GLOBAL_SCOPE_ID = 0L; + + private static final short ACTIVE_STATUS = 1; + + private static final short CANCELED_STATUS = 0; + + private static final short USER_OPERATION_SOURCE = 1; + + @Resource + private UserService userService; + + @Override + @Transactional(rollbackFor = Exception.class) + public void blockUser(Long blockerUserId, Long blockedUserId) { + validateUserIds(blockerUserId, blockedUserId); + validateBlockedUserExists(blockedUserId); + KeyboardCommentBlockRelation relation = getRelation(blockerUserId, blockedUserId); + if (relation == null) { + saveNewRelation(blockerUserId, blockedUserId); + return; + } + if (isActive(relation)) { + return; + } + reactivateRelation(relation); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void unblockUser(Long blockerUserId, Long blockedUserId) { + validateUserIds(blockerUserId, blockedUserId); + KeyboardCommentBlockRelation relation = getRelation(blockerUserId, blockedUserId); + if (relation == null || !isActive(relation)) { + return; + } + deactivateRelation(relation); + } + + @Override + public List listBlockedUsers(Long blockerUserId) { + if (blockerUserId == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + List relations = listActiveRelations(blockerUserId); + if (relations.isEmpty()) { + return List.of(); + } + Map userMap = getBlockedUserMap(relations); + return relations.stream() + .map(relation -> toBlockedUserVO(relation, userMap.get(relation.getBlockedUserId()))) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private void validateUserIds(Long blockerUserId, Long blockedUserId) { + if (blockerUserId == null || blockedUserId == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + if (blockerUserId.equals(blockedUserId)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "不能拉黑自己"); + } + } + + private void validateBlockedUserExists(Long blockedUserId) { + if (userService.getById(blockedUserId) == null) { + throw new BusinessException(ErrorCode.USER_NOT_FOUND); + } + } + + private KeyboardCommentBlockRelation getRelation(Long blockerUserId, Long blockedUserId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(KeyboardCommentBlockRelation::getBlockerUserId, blockerUserId) + .eq(KeyboardCommentBlockRelation::getBlockedUserId, blockedUserId) + .eq(KeyboardCommentBlockRelation::getScopeType, GLOBAL_SCOPE_TYPE) + .eq(KeyboardCommentBlockRelation::getScopeId, GLOBAL_SCOPE_ID) + .last("limit 1"); + return this.getOne(queryWrapper, false); + } + + private void saveNewRelation(Long blockerUserId, Long blockedUserId) { + Date now = new Date(); + KeyboardCommentBlockRelation relation = new KeyboardCommentBlockRelation(); + relation.setBlockerUserId(blockerUserId); + relation.setBlockedUserId(blockedUserId); + relation.setScopeType(GLOBAL_SCOPE_TYPE); + relation.setScopeId(GLOBAL_SCOPE_ID); + relation.setStatus(ACTIVE_STATUS); + relation.setSource(USER_OPERATION_SOURCE); + relation.setCreatedAt(now); + relation.setUpdatedAt(now); + boolean saved = this.save(relation); + if (!saved) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "拉黑用户失败"); + } + } + + private void reactivateRelation(KeyboardCommentBlockRelation relation) { + relation.setStatus(ACTIVE_STATUS); + relation.setSource(USER_OPERATION_SOURCE); + relation.setDeletedAt(null); + relation.setUpdatedAt(new Date()); + boolean updated = this.updateById(relation); + if (!updated) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "拉黑用户失败"); + } + } + + private void deactivateRelation(KeyboardCommentBlockRelation relation) { + Date now = new Date(); + relation.setStatus(CANCELED_STATUS); + relation.setUpdatedAt(now); + relation.setDeletedAt(now); + boolean updated = this.updateById(relation); + if (!updated) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "取消拉黑失败"); + } + } + + private List listActiveRelations(Long blockerUserId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(KeyboardCommentBlockRelation::getBlockerUserId, blockerUserId) + .eq(KeyboardCommentBlockRelation::getScopeType, GLOBAL_SCOPE_TYPE) + .eq(KeyboardCommentBlockRelation::getScopeId, GLOBAL_SCOPE_ID) + .eq(KeyboardCommentBlockRelation::getStatus, ACTIVE_STATUS) + .orderByDesc(KeyboardCommentBlockRelation::getUpdatedAt) + .orderByDesc(KeyboardCommentBlockRelation::getCreatedAt); + return this.list(queryWrapper); + } + + private Map getBlockedUserMap(List relations) { + List userIds = relations.stream() + .map(KeyboardCommentBlockRelation::getBlockedUserId) + .distinct() + .collect(Collectors.toList()); + return userService.listByIds(userIds).stream() + .collect(Collectors.toMap(KeyboardUser::getId, Function.identity())); + } + + private CommentBlockedUserVO toBlockedUserVO(KeyboardCommentBlockRelation relation, KeyboardUser user) { + if (user == null) { + return null; + } + CommentBlockedUserVO vo = new CommentBlockedUserVO(); + vo.setUserId(user.getId()); + vo.setUserUid(user.getUid()); + vo.setUserName(user.getNickName()); + vo.setUserAvatar(user.getAvatarUrl()); + vo.setBlockedAt(relation.getUpdatedAt() != null ? relation.getUpdatedAt() : relation.getCreatedAt()); + return vo; + } + + private boolean isActive(KeyboardCommentBlockRelation relation) { + return relation.getStatus() != null && relation.getStatus() == ACTIVE_STATUS; + } +} diff --git a/src/main/resources/mapper/KeyboardCommentBlockRelationMapper.xml b/src/main/resources/mapper/KeyboardCommentBlockRelationMapper.xml new file mode 100644 index 0000000..7cb16db --- /dev/null +++ b/src/main/resources/mapper/KeyboardCommentBlockRelationMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + id, blocker_user_id, blocked_user_id, scope_type, scope_id, "status", "source", created_at, + updated_at, deleted_at + + \ No newline at end of file