feat(backend): Improve authorization governance and video interactions
This commit is contained in:
+15
-2
@@ -3,6 +3,7 @@ package com.nuaa.video.comment.controller;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.comment.entity.Comment;
|
||||
import com.nuaa.video.comment.service.CommentService;
|
||||
import com.nuaa.video.comment.vo.AdminCommentVO;
|
||||
import com.nuaa.video.comment.vo.CommentStatVO;
|
||||
import com.nuaa.video.comment.vo.CommentVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
@@ -30,18 +31,19 @@ public class AdminCommentController {
|
||||
* 获取评论列表 - 管理接口
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<Page<CommentVO>> getCommentList(
|
||||
public Result<Page<AdminCommentVO>> getCommentList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "videoId", required = false) Long videoId,
|
||||
@RequestParam(value = "userId", required = false) Long userId,
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
return commentService.getAdminCommentList(page, size, videoId, userId, keyword);
|
||||
return commentService.getAdminCommentList(page, size, videoId, userId, status, keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +68,17 @@ public class AdminCommentController {
|
||||
return commentService.adminDeleteComment(commentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复评论 - 管理接口
|
||||
*/
|
||||
@PutMapping("/{commentId}/restore")
|
||||
public Result<Void> restoreComment(@PathVariable Long commentId, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return commentService.adminRestoreComment(commentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评论统计信息 - 管理接口
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* 用户服务Feign客户端
|
||||
@@ -35,5 +36,7 @@ public interface UserFeignClient {
|
||||
@PostMapping("/user/notification/send/{userId}/{type}")
|
||||
Result<Void> sendNotification(@PathVariable("userId") Long userId,
|
||||
@PathVariable("type") Integer type,
|
||||
@RequestParam(value = "content", required = false) String content,
|
||||
@RequestParam(value = "relatedId", required = false) Long relatedId,
|
||||
@RequestHeader("X-Internal-Token") String internalToken);
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeign
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> sendNotification(Long userId, Integer type, String internalToken) {
|
||||
public Result<Void> sendNotification(Long userId, Integer type, String content, Long relatedId, String internalToken) {
|
||||
log.error("发送用户通知失败,用户ID:{}, 类型:{}", userId, type);
|
||||
return Result.error("发送用户通知失败");
|
||||
}
|
||||
|
||||
+8
-2
@@ -7,6 +7,7 @@ import com.nuaa.video.comment.dto.CommentQueryDTO;
|
||||
import com.nuaa.video.comment.entity.Comment;
|
||||
import com.nuaa.video.comment.vo.CommentStatVO;
|
||||
import com.nuaa.video.comment.vo.CommentVO;
|
||||
import com.nuaa.video.comment.vo.AdminCommentVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
|
||||
/**
|
||||
@@ -99,7 +100,7 @@ public interface CommentService extends IService<Comment> {
|
||||
/**
|
||||
* 获取评论列表 - 管理接口
|
||||
*/
|
||||
Result<Page<CommentVO>> getAdminCommentList(Integer page, Integer size, Long videoId, Long userId, String keyword);
|
||||
Result<Page<AdminCommentVO>> getAdminCommentList(Integer page, Integer size, Long videoId, Long userId, Integer status, String keyword);
|
||||
|
||||
/**
|
||||
* 获取评论详情 - 管理接口
|
||||
@@ -111,8 +112,13 @@ public interface CommentService extends IService<Comment> {
|
||||
*/
|
||||
Result<Void> adminDeleteComment(Long commentId);
|
||||
|
||||
/**
|
||||
* 管理员恢复评论 - 管理接口
|
||||
*/
|
||||
Result<Void> adminRestoreComment(Long commentId);
|
||||
|
||||
/**
|
||||
* 获取评论统计信息 - 管理接口
|
||||
*/
|
||||
Result<CommentStatVO> getCommentStats();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -277,10 +277,10 @@ public class CommentReplyServiceImpl extends ServiceImpl<CommentReplyMapper, Com
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
if (!userId.equals(comment.getUserId())) {
|
||||
userFeignClient.sendNotification(comment.getUserId(), 2, internalServiceToken);
|
||||
userFeignClient.sendNotification(comment.getUserId(), 2, null, replyDTO.getVideoId(), internalServiceToken);
|
||||
}
|
||||
if (replyDTO.getReplyToId() != null && !userId.equals(replyDTO.getReplyToId())) {
|
||||
userFeignClient.sendNotification(replyDTO.getReplyToId(), 2, internalServiceToken);
|
||||
userFeignClient.sendNotification(replyDTO.getReplyToId(), 2, null, replyDTO.getVideoId(), internalServiceToken);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("发送回复通知失败", e);
|
||||
|
||||
+58
-8
@@ -17,6 +17,7 @@ import com.nuaa.video.comment.service.CommentService;
|
||||
import com.nuaa.video.comment.utils.SensitiveWordFilter;
|
||||
import com.nuaa.video.comment.vo.CommentStatVO;
|
||||
import com.nuaa.video.comment.vo.CommentVO;
|
||||
import com.nuaa.video.comment.vo.AdminCommentVO;
|
||||
import com.nuaa.video.comment.vo.ReplyVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -94,7 +95,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
||||
|
||||
updateVideoCommentCountAsync(commentDTO.getVideoId(), 1);
|
||||
|
||||
sendCommentNotificationAsync(videoResult.getData(), userId);
|
||||
sendCommentNotificationAsync(videoResult.getData(), userId, commentDTO.getVideoId());
|
||||
|
||||
return Result.success(comment.getId());
|
||||
}
|
||||
@@ -361,7 +362,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Page<CommentVO>> getAdminCommentList(Integer page, Integer size, Long videoId, Long userId, String keyword) {
|
||||
public Result<Page<AdminCommentVO>> getAdminCommentList(Integer page, Integer size, Long videoId, Long userId, Integer status, String keyword) {
|
||||
if (page == null || page < 1) {
|
||||
page = 1;
|
||||
}
|
||||
@@ -372,7 +373,9 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 管理员可以查看所有状态的评论,包括已删除的
|
||||
if (status != null) {
|
||||
queryWrapper.eq(Comment::getStatus, status);
|
||||
}
|
||||
|
||||
// 按视频ID筛选
|
||||
if (videoId != null) {
|
||||
@@ -397,12 +400,12 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
||||
commentPage = page(commentPage, queryWrapper);
|
||||
|
||||
// 转换为VO
|
||||
Page<CommentVO> voPage = new Page<>();
|
||||
Page<AdminCommentVO> voPage = new Page<>();
|
||||
BeanUtils.copyProperties(commentPage, voPage, "records");
|
||||
|
||||
List<CommentVO> commentVOList = new ArrayList<>();
|
||||
List<AdminCommentVO> commentVOList = new ArrayList<>();
|
||||
for (Comment comment : commentPage.getRecords()) {
|
||||
CommentVO vo = convertToCommentVO(comment, null);
|
||||
AdminCommentVO vo = convertToAdminCommentVO(comment);
|
||||
commentVOList.add(vo);
|
||||
}
|
||||
|
||||
@@ -453,6 +456,23 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> adminRestoreComment(Long commentId) {
|
||||
Comment comment = getById(commentId);
|
||||
if (comment == null) {
|
||||
return Result.error("评论不存在");
|
||||
}
|
||||
if (comment.getStatus() == 1) {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
commentMapper.updateCommentStatus(commentId, 1, "admin");
|
||||
updateVideoCommentCountAsync(comment.getVideoId(), 1);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<CommentStatVO> getCommentStats() {
|
||||
CommentStatVO statVO = new CommentStatVO();
|
||||
@@ -514,14 +534,44 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
||||
return vo;
|
||||
}
|
||||
|
||||
private void sendCommentNotificationAsync(Object videoPayload, Long userId) {
|
||||
private AdminCommentVO convertToAdminCommentVO(Comment comment) {
|
||||
AdminCommentVO vo = new AdminCommentVO();
|
||||
BeanUtils.copyProperties(comment, vo);
|
||||
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(comment.getUserId());
|
||||
if (userResult.getCode() == 200 && userResult.getData() != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||
vo.setUserNickname((String) userData.get("nickname"));
|
||||
vo.setUserAvatar((String) userData.get("avatar"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("管理端获取评论用户信息失败, userId={}", comment.getUserId(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
Result<Object> videoResult = contentFeignClient.getVideoInfo(comment.getVideoId());
|
||||
if (videoResult.getCode() == 200 && videoResult.getData() != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> videoData = (Map<String, Object>) videoResult.getData();
|
||||
vo.setVideoTitle((String) videoData.get("title"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("管理端获取视频信息失败, videoId={}", comment.getVideoId(), e);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
private void sendCommentNotificationAsync(Object videoPayload, Long userId, Long videoId) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> videoData = (Map<String, Object>) videoPayload;
|
||||
Long videoAuthorId = Long.valueOf(videoData.get("userId").toString());
|
||||
if (!userId.equals(videoAuthorId)) {
|
||||
userFeignClient.sendNotification(videoAuthorId, 2, internalServiceToken);
|
||||
userFeignClient.sendNotification(videoAuthorId, 2, null, videoId, internalServiceToken);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("发送评论通知失败", e);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nuaa.video.comment.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 管理端评论VO
|
||||
*/
|
||||
@Data
|
||||
public class AdminCommentVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long videoId;
|
||||
|
||||
private String videoTitle;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String userNickname;
|
||||
|
||||
private String userAvatar;
|
||||
|
||||
private String content;
|
||||
|
||||
private Integer likeCount;
|
||||
|
||||
private Integer replyCount;
|
||||
|
||||
private Integer topStatus;
|
||||
|
||||
/**
|
||||
* 状态(0:已删除,1:正常)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.nuaa.video.comment.service;
|
||||
|
||||
import com.nuaa.video.comment.dto.ReplyAddDTO;
|
||||
import com.nuaa.video.comment.entity.Comment;
|
||||
import com.nuaa.video.comment.feign.UserFeignClient;
|
||||
import com.nuaa.video.comment.service.impl.CommentReplyServiceImpl;
|
||||
import com.nuaa.video.comment.service.impl.CommentServiceImpl;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("通知发送测试")
|
||||
class NotificationDispatchTest {
|
||||
|
||||
@Mock
|
||||
private UserFeignClient userFeignClient;
|
||||
|
||||
@InjectMocks
|
||||
private CommentServiceImpl commentService;
|
||||
|
||||
@InjectMocks
|
||||
private CommentReplyServiceImpl commentReplyService;
|
||||
|
||||
@Test
|
||||
@DisplayName("评论通知应携带视频ID作为 relatedId")
|
||||
void shouldSendCommentNotificationWithVideoId() {
|
||||
ReflectionTestUtils.setField(commentService, "internalServiceToken", "internal-token");
|
||||
|
||||
ReflectionTestUtils.invokeMethod(
|
||||
commentService,
|
||||
"sendCommentNotificationAsync",
|
||||
Map.of("userId", 200L),
|
||||
100L,
|
||||
300L
|
||||
);
|
||||
|
||||
verify(userFeignClient, timeout(1000)).sendNotification(200L, 2, null, 300L, "internal-token");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("回复通知应携带视频ID作为 relatedId")
|
||||
void shouldSendReplyNotificationWithVideoId() {
|
||||
ReflectionTestUtils.setField(commentReplyService, "internalServiceToken", "internal-token");
|
||||
|
||||
Comment comment = new Comment();
|
||||
comment.setUserId(200L);
|
||||
|
||||
ReplyAddDTO replyDTO = new ReplyAddDTO();
|
||||
replyDTO.setReplyToId(300L);
|
||||
replyDTO.setVideoId(400L);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(
|
||||
commentReplyService,
|
||||
"sendReplyNotificationAsync",
|
||||
comment,
|
||||
replyDTO,
|
||||
100L
|
||||
);
|
||||
|
||||
verify(userFeignClient, timeout(1000)).sendNotification(eq(200L), eq(2), isNull(), eq(400L), eq("internal-token"));
|
||||
verify(userFeignClient, timeout(1000)).sendNotification(eq(300L), eq(2), isNull(), eq(400L), eq("internal-token"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user