点赞,评论,关注,私信,在线消息通知,匿名发帖(目前是全局公用的匿名名称与头像url)

main
forely 2 weeks ago
parent e562fe9004
commit 7f2c6fd73e

@ -16,8 +16,10 @@ public class WebMvcConfig implements WebMvcConfigurer {
registry.addInterceptor(authInterceptor) registry.addInterceptor(authInterceptor)
.excludePathPatterns("/user/login", .excludePathPatterns("/user/login",
"/user/register", "/user/register",
"/post/page", "/post/list",
"/post/detail" "/post/detail",
"/comment/list",
"/comment/list/reply"
); );
} }

@ -16,10 +16,7 @@ public final class UserContext {
public static Long getUserId() { public static Long getUserId() {
UserDTO userInfoDTO = USER_THREAD_LOCAL.get(); UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
if(userInfoDTO == null){ return Optional.ofNullable(userInfoDTO).map(UserDTO::getUserId).orElse(null);
throw new UserException("用户不存在");
}
return userInfoDTO.getUserId();
} }
public static String getUsername() { public static String getUsername() {

@ -0,0 +1,25 @@
package com.luojia_channel.modules.message.mq.config;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory, MessageConverter jsonMessageConverter) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jsonMessageConverter);
return factory;
}
}

@ -1,10 +1,13 @@
package com.luojia_channel.modules.message.mq.consumer; package com.luojia_channel.modules.message.mq.consumer;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.luojia_channel.modules.message.dto.MessageRequest; import com.luojia_channel.modules.message.dto.MessageRequest;
import com.luojia_channel.modules.message.mq.MessageWrapper;
import com.luojia_channel.modules.message.mq.domain.NotificationMessage; import com.luojia_channel.modules.message.mq.domain.NotificationMessage;
import com.luojia_channel.modules.message.server.WebSocketServer; import com.luojia_channel.modules.message.server.WebSocketServer;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.QueueBinding;
@ -13,6 +16,7 @@ import org.springframework.stereotype.Component;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class NotificationListener { public class NotificationListener {
public static final String EXCHANGE_NAME = "notify.exchange"; public static final String EXCHANGE_NAME = "notify.exchange";
public static final String QUEUE_NAME = "notify.queue"; public static final String QUEUE_NAME = "notify.queue";
@ -24,13 +28,20 @@ public class NotificationListener {
exchange = @Exchange(name = EXCHANGE_NAME, type = "direct"), exchange = @Exchange(name = EXCHANGE_NAME, type = "direct"),
key = ROUTING_KEY key = ROUTING_KEY
)) ))
public void handleNotification(MessageWrapper<NotificationMessage> wrapper) {
public void handleNotification(NotificationMessage message) { try {
NotificationMessage message = wrapper.getMessage();
MessageRequest request = BeanUtil.copyProperties(message, MessageRequest.class); MessageRequest request = BeanUtil.copyProperties(message, MessageRequest.class);
if (message.getMessageType() == 0) { Integer messageType = message.getMessageType();
if (messageType != null && messageType == 0) {
webSocketServer.sendPrivateMessage(message.getSenderId(), request); webSocketServer.sendPrivateMessage(message.getSenderId(), request);
} else { } else {
webSocketServer.sendSystemNotification(request); webSocketServer.sendSystemNotification(request);
} }
} catch (Exception e) {
// 记录异常日志
log.error("处理消息时发生异常: {}", e.getMessage(), e);
throw e;
}
} }
} }

@ -5,6 +5,8 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor

@ -96,7 +96,7 @@ public class WebSocketServer {
if (receiverSession != null && receiverSession.isOpen()) { if (receiverSession != null && receiverSession.isOpen()) {
sendMessage(receiverSession, JSON.toJSONString(response)); sendMessage(receiverSession, JSON.toJSONString(response));
} else { } else {
log.warn("接收方 [{}] 不在线,消息无法即时送达", receiverId); log.info("接收方 [{}] 不在线,消息无法即时送达", receiverId);
} }
MessageDO message = BeanUtil.copyProperties(response, MessageDO.class); MessageDO message = BeanUtil.copyProperties(response, MessageDO.class);
messageMapper.insert(message); messageMapper.insert(message);

@ -17,7 +17,7 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
@RestController @RestController
@RequestMapping("/comments") @RequestMapping("/comment")
public class CommentController { public class CommentController {
private final CommentService commentService; private final CommentService commentService;
@ -58,4 +58,11 @@ public class CommentController {
return Result.success(commentInfoDTOList); return Result.success(commentInfoDTOList);
} }
// 点赞评论
@PutMapping("/like/{id}")
public Result<Void> likeComment(@PathVariable("id") Long id) {
commentService.likeComment(id);
return Result.success();
}
} }

@ -7,7 +7,6 @@ import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO; import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO; import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.dto.resp.PostInfoDTO; import com.luojia_channel.modules.post.dto.resp.PostInfoDTO;
import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.service.PostService; import com.luojia_channel.modules.post.service.PostService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -22,8 +21,8 @@ public class PostController {
// 创建帖子 // 创建帖子
@PostMapping @PostMapping
public Result<Void> createPost(@RequestBody PostSaveDTO postSaveDTO) { public Result<Void> savePost(@RequestBody PostSaveDTO postSaveDTO) {
postService.createPost(postSaveDTO); postService.savePost(postSaveDTO);
return Result.success(); return Result.success();
} }
@ -64,4 +63,11 @@ public class PostController {
public Result<PageResponse<PostBasicInfoDTO>> pagePostOfMe(@RequestBody PostPageQueryDTO postPageQueryDTO) { public Result<PageResponse<PostBasicInfoDTO>> pagePostOfMe(@RequestBody PostPageQueryDTO postPageQueryDTO) {
return Result.success(postService.pagePostOfMe(postPageQueryDTO)); return Result.success(postService.pagePostOfMe(postPageQueryDTO));
} }
// 点赞帖子
@PutMapping("/like/{id}")
public Result<Void> likePost(@PathVariable("id") Long id) {
postService.likePost(id);
return Result.success();
}
} }

@ -23,6 +23,8 @@ public class CommentInfoDTO {
private Long topId; private Long topId;
private LocalDateTime createTime; private LocalDateTime createTime;
private Boolean isLike;
private String userName; private String userName;
private String userAvatar; private String userAvatar;

@ -21,4 +21,6 @@ public interface CommentService {
PageResponse<CommentInfoDTO> getCommentsByPostId(CommentPageQueryDTO commentPageQueryDTO); PageResponse<CommentInfoDTO> getCommentsByPostId(CommentPageQueryDTO commentPageQueryDTO);
PageResponse<CommentInfoDTO> getReplyById(CommentPageQueryDTO commentPageQueryDTO); PageResponse<CommentInfoDTO> getReplyById(CommentPageQueryDTO commentPageQueryDTO);
void likeComment(Long id);
} }

@ -10,7 +10,7 @@ import com.luojia_channel.modules.post.entity.Post;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
public interface PostService extends IService<Post> { public interface PostService extends IService<Post> {
void createPost(PostSaveDTO postSaveDTO); void savePost(PostSaveDTO postSaveDTO);
String setCover(MultipartFile file); String setCover(MultipartFile file);
@ -23,4 +23,6 @@ public interface PostService extends IService<Post> {
PageResponse<PostBasicInfoDTO> pagePost(PostPageQueryDTO postPageQueryDTO); PageResponse<PostBasicInfoDTO> pagePost(PostPageQueryDTO postPageQueryDTO);
PageResponse<PostBasicInfoDTO> pagePostOfMe(PostPageQueryDTO postPageQueryDTO); PageResponse<PostBasicInfoDTO> pagePostOfMe(PostPageQueryDTO postPageQueryDTO);
void likePost(Long id);
} }

@ -8,25 +8,28 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.domain.page.PageResponse; import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.common.exception.PostException; import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.PageUtil; import com.luojia_channel.common.utils.PageUtil;
import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.common.utils.UserContext; import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.message.mq.domain.NotificationMessage; import com.luojia_channel.modules.message.mq.domain.NotificationMessage;
import com.luojia_channel.modules.post.dto.req.CommentPageQueryDTO; import com.luojia_channel.modules.post.dto.req.CommentPageQueryDTO;
import com.luojia_channel.modules.post.dto.req.CommentSaveDTO; import com.luojia_channel.modules.post.dto.req.CommentSaveDTO;
import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO; import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.entity.Comment; import com.luojia_channel.modules.post.entity.Comment;
import com.luojia_channel.modules.post.entity.Post; import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.mapper.CommentMapper; import com.luojia_channel.modules.post.mapper.CommentMapper;
import com.luojia_channel.modules.post.mapper.PostMapper;
import com.luojia_channel.modules.post.mq.producer.NotificationProducer; import com.luojia_channel.modules.post.mq.producer.NotificationProducer;
import com.luojia_channel.modules.post.service.CommentService; import com.luojia_channel.modules.post.service.CommentService;
import com.luojia_channel.modules.post.utils.ValidatePostUtil; import com.luojia_channel.modules.post.utils.ValidatePostUtil;
import com.luojia_channel.modules.user.entity.User; import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.mapper.UserMapper; import com.luojia_channel.modules.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -38,6 +41,7 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService { public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@ -45,19 +49,49 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
private final CommentMapper commentMapper; private final CommentMapper commentMapper;
private final UserMapper userMapper; private final UserMapper userMapper;
private final NotificationProducer notificationProducer; private final NotificationProducer notificationProducer;
private final RedisUtil redisUtil;
private final PostMapper postMapper;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void saveComment(CommentSaveDTO commentSaveDTO) { public void saveComment(CommentSaveDTO commentSaveDTO) {
Long userId = UserContext.getUserId();
if(userId == null){
throw new UserException("用户未登录");
}
validatePostUtil.validateComment(commentSaveDTO); validatePostUtil.validateComment(commentSaveDTO);
Comment comment = BeanUtil.copyProperties(commentSaveDTO, Comment.class); Comment comment = BeanUtil.copyProperties(commentSaveDTO, Comment.class);
comment.setUserId(UserContext.getUserId());
comment.setCreateTime(LocalDateTime.now()); comment.setCreateTime(LocalDateTime.now());
comment.setUpdateTime(LocalDateTime.now()); comment.setUpdateTime(LocalDateTime.now());
if(!save(comment)){ if(!save(comment)){
throw new PostException("创建评论失败"); throw new PostException("创建评论失败");
} }
Long postId = commentSaveDTO.getPostId();
Post post = postMapper.selectById(postId);
if (post == null) {
throw new PostException("回复的帖子不存在");
}
Long receiveUserId = post.getUserId();
Long parentCommentId = commentSaveDTO.getParentCommentId(); Long parentCommentId = commentSaveDTO.getParentCommentId();
// 消息通知,回复帖子
if(!userId.equals(receiveUserId) && parentCommentId == null) {
String content = String.format("%s 回复了你的帖子: %s",
UserContext.getUsername(),
StringUtils.abbreviate(commentSaveDTO.getContent(), 20));
NotificationMessage notificationMessage = NotificationMessage.builder()
.senderId(UserContext.getUserId())
.senderName(UserContext.getUsername())
.senderAvatar(UserContext.getAvatar())
.receiverId(receiveUserId)
.content(content)
.messageType(0)
.build();
notificationProducer.sendMessage(notificationMessage);
}
if(parentCommentId != null){ if(parentCommentId != null){
// 是回复的评论
Comment partentComment = commentMapper.selectById(parentCommentId); Comment partentComment = commentMapper.selectById(parentCommentId);
partentComment.setReplyCount(partentComment.getReplyCount() + 1); partentComment.setReplyCount(partentComment.getReplyCount() + 1);
int update = commentMapper.updateById(partentComment); int update = commentMapper.updateById(partentComment);
@ -75,7 +109,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
throw new PostException("回复顶级评论失败"); throw new PostException("回复顶级评论失败");
} }
} }
// 消息通知 // 消息通知,回复评论
String content = String.format("%s 回复了你的评论: %s", String content = String.format("%s 回复了你的评论: %s",
UserContext.getUsername(), UserContext.getUsername(),
StringUtils.abbreviate(commentSaveDTO.getContent(), 20)); StringUtils.abbreviate(commentSaveDTO.getContent(), 20));
@ -85,6 +119,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
.senderAvatar(UserContext.getAvatar()) .senderAvatar(UserContext.getAvatar())
.receiverId(partentComment.getUserId()) .receiverId(partentComment.getUserId())
.content(content) .content(content)
.messageType(0)
.build(); .build();
notificationProducer.sendMessage(notificationMessage); notificationProducer.sendMessage(notificationMessage);
} }
@ -95,6 +130,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
validatePostUtil.validateComment(commentSaveDTO); validatePostUtil.validateComment(commentSaveDTO);
validatePostUtil.validateCommentOwnership(commentSaveDTO.getId()); validatePostUtil.validateCommentOwnership(commentSaveDTO.getId());
Comment comment = BeanUtil.copyProperties(commentSaveDTO, Comment.class); Comment comment = BeanUtil.copyProperties(commentSaveDTO, Comment.class);
comment.setUserId(UserContext.getUserId());
comment.setUpdateTime(LocalDateTime.now()); comment.setUpdateTime(LocalDateTime.now());
if(!updateById(comment)){ if(!updateById(comment)){
throw new PostException("更新评论失败"); throw new PostException("更新评论失败");
@ -137,12 +173,45 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
return getCommentInfoDTOPageResponse(commentPageQueryDTO, queryWrapper); return getCommentInfoDTOPageResponse(commentPageQueryDTO, queryWrapper);
} }
@Override
public void likeComment(Long id) {
Long userId = UserContext.getUserId();
String likeCommentKey = "comment:like:" + id;
RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
// 检查是否未点赞
if (!isLikedComment(id)) {
// 数据库点赞记录加一
boolean success = updateCommentLikeCount(id, 1);
if (success) {
redisTemplate.opsForSet().add(likeCommentKey, userId, System.currentTimeMillis());
}
} else {
// 数据库点赞记录减一
boolean success = updateCommentLikeCount(id, -1);
if (success) {
redisTemplate.opsForSet().remove(likeCommentKey, userId);
}
}
}
private boolean isLikedComment(Long commentId) {
Long userId = UserContext.getUserId();
String likeCommentKey = "comment:like:" + commentId;
RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeCommentKey, userId));
}
private boolean updateCommentLikeCount(Long commentId, int increment) {
LambdaUpdateWrapper<Comment> updateWrapper = Wrappers.lambdaUpdate(Comment.class)
.setSql("like_count = like_count + " + increment)
.eq(Comment::getId, commentId);
return commentMapper.update(null, updateWrapper) > 0;
}
private PageResponse<CommentInfoDTO> getCommentInfoDTOPageResponse(CommentPageQueryDTO commentPageQueryDTO, LambdaQueryWrapper<Comment> queryWrapper) { private PageResponse<CommentInfoDTO> getCommentInfoDTOPageResponse(CommentPageQueryDTO commentPageQueryDTO, LambdaQueryWrapper<Comment> queryWrapper) {
IPage<Comment> commentPage = commentMapper.selectPage(PageUtil.convert(commentPageQueryDTO), queryWrapper); IPage<Comment> commentPage = commentMapper.selectPage(PageUtil.convert(commentPageQueryDTO), queryWrapper);
List<Long> userIds = new ArrayList<>(); List<Long> userIds = new ArrayList<>();
commentPage.getRecords().forEach(comment -> { commentPage.getRecords().forEach(comment -> userIds.add(comment.getUserId()));
userIds.add(comment.getUserId());
});
List<User> users = userMapper.selectBatchIds(userIds); List<User> users = userMapper.selectBatchIds(userIds);
Map<Long, User> userMap = users.stream() Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, user -> user)); .collect(Collectors.toMap(User::getId, user -> user));
@ -151,6 +220,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
User user = userMap.getOrDefault(comment.getUserId(), new User()); User user = userMap.getOrDefault(comment.getUserId(), new User());
commentInfoDTO.setUserAvatar(user.getAvatar()); commentInfoDTO.setUserAvatar(user.getAvatar());
commentInfoDTO.setUserName(user.getUsername()); commentInfoDTO.setUserName(user.getUsername());
commentInfoDTO.setIsLike(isLikedComment(comment.getId()));
return commentInfoDTO; return commentInfoDTO;
}); });
} }

@ -7,17 +7,15 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.domain.page.PageResponse; import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.common.exception.PostException; import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.PageUtil; import com.luojia_channel.common.utils.PageUtil;
import com.luojia_channel.common.utils.RedisUtil; import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.common.utils.UserContext; import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.file.service.impl.FileServiceImpl; import com.luojia_channel.modules.file.service.impl.FileServiceImpl;
import com.luojia_channel.modules.post.dto.req.CommentPageQueryDTO;
import com.luojia_channel.modules.post.dto.req.PostSaveDTO; import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO; import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO; import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.dto.resp.PostInfoDTO; import com.luojia_channel.modules.post.dto.resp.PostInfoDTO;
import com.luojia_channel.modules.post.entity.Comment;
import com.luojia_channel.modules.post.entity.Post; import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.mapper.PostMapper; import com.luojia_channel.modules.post.mapper.PostMapper;
import com.luojia_channel.modules.post.service.PostService; import com.luojia_channel.modules.post.service.PostService;
@ -25,6 +23,7 @@ import com.luojia_channel.modules.post.utils.ValidatePostUtil;
import com.luojia_channel.modules.user.entity.User; import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.mapper.UserMapper; import com.luojia_channel.modules.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -44,15 +43,26 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
private final RedisUtil redisUtil; private final RedisUtil redisUtil;
private final UserMapper userMapper; private final UserMapper userMapper;
// 匿名用户名与匿名头像
private static final String ANONYMOUS_NAME = "匿名用户";
private static final String ANONYMOUS_AVATAR = "";
@Override @Override
public void createPost(PostSaveDTO postSaveDTO) { public void savePost(PostSaveDTO postSaveDTO) {
Long userId = UserContext.getUserId();
if(userId == null){
throw new UserException("用户未登录");
}
validatePostUtil.validatePost(postSaveDTO); validatePostUtil.validatePost(postSaveDTO);
Post post = BeanUtil.copyProperties(postSaveDTO, Post.class); Post post = BeanUtil.copyProperties(postSaveDTO, Post.class);
post.setUserId(UserContext.getUserId());
post.setCreateTime(LocalDateTime.now()); post.setCreateTime(LocalDateTime.now());
post.setUpdateTime(LocalDateTime.now()); post.setUpdateTime(LocalDateTime.now());
if(!save(post)){ if(!save(post)){
throw new PostException("创建帖子失败"); throw new PostException("创建帖子失败");
} }
redisUtil.delete("post:detail:" + postSaveDTO.getId());
redisUtil.delete("post:of:user:" + UserContext.getUserId());
} }
@Override @Override
@ -65,10 +75,13 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
validatePostUtil.validatePost(postSaveDTO); validatePostUtil.validatePost(postSaveDTO);
validatePostUtil.validatePostOwnership(postSaveDTO.getId()); validatePostUtil.validatePostOwnership(postSaveDTO.getId());
Post post = BeanUtil.copyProperties(postSaveDTO, Post.class); Post post = BeanUtil.copyProperties(postSaveDTO, Post.class);
post.setUserId(UserContext.getUserId());
post.setUpdateTime(LocalDateTime.now()); post.setUpdateTime(LocalDateTime.now());
if(!updateById(post)){ if(!updateById(post)){
throw new PostException("更新帖子失败"); throw new PostException("更新帖子失败");
} }
redisUtil.delete("post:detail:" + postSaveDTO.getId());
redisUtil.delete("post:of:user:" + UserContext.getUserId());
} }
@Override @Override
@ -78,11 +91,13 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
if(delete <= 0){ if(delete <= 0){
throw new PostException("删除帖子失败"); throw new PostException("删除帖子失败");
} }
redisUtil.delete("post:detail:" + id.toString());
redisUtil.delete("post:of:user:" + UserContext.getUserId());
} }
@Override @Override
public PostInfoDTO getPostDetail(Long id) { public PostInfoDTO getPostDetail(Long id) {
return redisUtil.safeGet("post:detail" + id.toString(), PostInfoDTO.class, return redisUtil.safeGet("post:detail:" + id, PostInfoDTO.class,
() -> { () -> {
Post post = getById(id); Post post = getById(id);
if(post == null){ if(post == null){
@ -93,6 +108,11 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
.eq(User::getId, post.getUserId())); .eq(User::getId, post.getUserId()));
postInfoDTO.setUserAvatar(user.getAvatar()); postInfoDTO.setUserAvatar(user.getAvatar());
postInfoDTO.setUserName(user.getUsername()); postInfoDTO.setUserName(user.getUsername());
if (post.getStatus() == 1) { // 匿名帖子
postInfoDTO.setUserName(ANONYMOUS_NAME);
postInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
}
postInfoDTO.setIsLike(isLikedPost(post.getId()));
return postInfoDTO; return postInfoDTO;
}, },
60, TimeUnit.MINUTES); 60, TimeUnit.MINUTES);
@ -105,9 +125,7 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
.orderByDesc(Post::getCreateTime); .orderByDesc(Post::getCreateTime);
IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper); IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper);
List<Long> userIds = new ArrayList<>(); List<Long> userIds = new ArrayList<>();
postPage.getRecords().forEach(comment -> { postPage.getRecords().forEach(comment -> userIds.add(comment.getUserId()));
userIds.add(comment.getUserId());
});
List<User> users = userMapper.selectBatchIds(userIds); List<User> users = userMapper.selectBatchIds(userIds);
Map<Long, User> userMap = users.stream() Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, user -> user)); .collect(Collectors.toMap(User::getId, user -> user));
@ -117,6 +135,10 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
User user = userMap.getOrDefault(post.getUserId(), new User()); User user = userMap.getOrDefault(post.getUserId(), new User());
postBasicInfoDTO.setUserAvatar(user.getAvatar()); postBasicInfoDTO.setUserAvatar(user.getAvatar());
postBasicInfoDTO.setUserName(user.getUsername()); postBasicInfoDTO.setUserName(user.getUsername());
if (post.getStatus() == 1) { // 匿名帖子
postBasicInfoDTO.setUserName(ANONYMOUS_NAME);
postBasicInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
}
return postBasicInfoDTO; return postBasicInfoDTO;
}); });
} }
@ -124,11 +146,63 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
@Override @Override
public PageResponse<PostBasicInfoDTO> pagePostOfMe(PostPageQueryDTO postPageQueryDTO) { public PageResponse<PostBasicInfoDTO> pagePostOfMe(PostPageQueryDTO postPageQueryDTO) {
Long userId = UserContext.getUserId(); Long userId = UserContext.getUserId();
if(userId == null){
throw new UserException("用户未登录");
}
// 构建包含分页信息的缓存键
String cacheKey = "post:of:user:" + userId + ":page:" + postPageQueryDTO.getCurrent() + ":size:" + postPageQueryDTO.getSize();
return redisUtil.safeGet(cacheKey, PageResponse.class,
() -> {
LambdaQueryWrapper<Post> queryWrapper = Wrappers.lambdaQuery(Post.class) LambdaQueryWrapper<Post> queryWrapper = Wrappers.lambdaQuery(Post.class)
.eq(Post::getUserId, userId) .eq(Post::getUserId, userId)
.orderByDesc(Post::getCreateTime); .orderByDesc(Post::getCreateTime);
IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper); IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper);
return PageUtil.convert(postPage, PostBasicInfoDTO.class); return PageUtil.convert(postPage, post -> {
PostBasicInfoDTO postBasicInfoDTO = BeanUtil.copyProperties(post, PostBasicInfoDTO.class);
postBasicInfoDTO.setUserAvatar(UserContext.getAvatar());
postBasicInfoDTO.setUserName(UserContext.getUsername());
if (post.getStatus() == 1) { // 匿名帖子
postBasicInfoDTO.setUserName(ANONYMOUS_NAME);
postBasicInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
}
return postBasicInfoDTO;
});
},
60, TimeUnit.MINUTES);
}
private boolean isLikedPost(Long postId){
Long userId = UserContext.getUserId();
if(userId == null){
return false;
}
String likeBlogKey = "post:like:" + postId;
RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeBlogKey, userId));
}
@Override
public void likePost(Long id) {
Long userId = UserContext.getUserId();
if(userId == null){
throw new UserException("用户未登录");
}
String likeBlogKey = "post:like:" + id;
RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
//没点赞
if(!isLikedPost(id)){
//数据库点赞记录加一
boolean success = update().setSql("liked = liked + 1").eq("id",id).update();
if(success){
redisTemplate.opsForSet().add(likeBlogKey, userId, System.currentTimeMillis());
}
}else{
//数据库点赞记录减一
boolean success = update().setSql("liked = liked - 1").eq("id",id).update();
if(success){
redisTemplate.opsForSet().remove(likeBlogKey, userId);
}
}
} }
} }

@ -2,6 +2,7 @@ package com.luojia_channel.modules.post.utils;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.luojia_channel.common.exception.PostException; import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.UserContext; import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.post.dto.req.CommentSaveDTO; import com.luojia_channel.modules.post.dto.req.CommentSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostSaveDTO; import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
@ -39,6 +40,9 @@ public class ValidatePostUtil {
public void validatePostOwnership(Long id){ public void validatePostOwnership(Long id){
Long userId = UserContext.getUserId(); Long userId = UserContext.getUserId();
if(userId == null){
throw new UserException("用户未登录");
}
if(id == null){ if(id == null){
throw new PostException("传入id不能为空"); throw new PostException("传入id不能为空");
} }
@ -68,6 +72,9 @@ public class ValidatePostUtil {
public void validateCommentOwnership(Long id) { public void validateCommentOwnership(Long id) {
Long userId = UserContext.getUserId(); Long userId = UserContext.getUserId();
if(userId == null){
throw new UserException("用户未登录");
}
if(id == null){ if(id == null){
throw new PostException("传入id不能为空"); throw new PostException("传入id不能为空");
} }

@ -101,10 +101,11 @@ DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` ( CREATE TABLE `comment` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`content` TEXT NOT NULL COMMENT '评论内容', `content` TEXT NOT NULL COMMENT '评论内容',
`like_count` INT DEFAULT 0 COMMENT '点赞数',
`reply_count` INT DEFAULT 0 COMMENT '回复数',
`user_id` BIGINT NOT NULL COMMENT '评论用户ID', `user_id` BIGINT NOT NULL COMMENT '评论用户ID',
`post_type` VARCHAR(20) NOT NULL COMMENT '帖子类型post/video',
`post_id` BIGINT NOT NULL COMMENT '关联的帖子ID', `post_id` BIGINT NOT NULL COMMENT '关联的帖子ID',
`parent_comment_id` BIGINT COMMENT '父评论ID', `parent_comment_id` BIGINT DEFAULT 0 COMMENT '父评论ID',
`top_id` BIGINT COMMENT '顶层评论ID', `top_id` BIGINT COMMENT '顶层评论ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
@ -151,3 +152,27 @@ CREATE TABLE `view_record` (
INDEX idx_post_type (post_type), INDEX idx_post_type (post_type),
INDEX idx_post_id (post_id) INDEX idx_post_id (post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浏览记录表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浏览记录表';
## 私信消息表
DROP TABLE IF EXISTS `message`;
CREATE TABLE message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
message_type TINYINT NOT NULL COMMENT '0-私聊, 1-系统消息',
content TEXT NOT NULL,
sender_id BIGINT NOT NULL,
receiver_id BIGINT NOT NULL,
create_time DATETIME NOT NULL,
INDEX idx_sender_id (sender_id),
INDEX idx_receiver_id (receiver_id),
INDEX idx_create_time (create_time)
);
## 关注表
DROP TABLE IF EXISTS `follow`;
CREATE TABLE `follow` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户id',
`follow_user_id` bigint(20) UNSIGNED NOT NULL COMMENT '关联的用户id',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关注表';

Loading…
Cancel
Save