分析
设计论坛模块时,需要提供一系列后端接口来支持用户和管理人员的各种需求。这些后端接口可以分为两类:用户App端接口和管理后台接口。
对于用户App端接口,我们的目标是提供一些常用的功能,例如创建帖子、评论,删除帖子、评论和查询帖子、评论等。这些接口将允许用户在论坛上发布内容、参与讨论和管理他们自己的帖子。我们还可以为用户提供其他附加功能,例如点赞、收藏等。
对于管理后台接口,我们的目标是提供一些管理功能,例如审核帖子、评论、置顶帖子和删除不当内容等。这些接口将使管理人员能够监控和管理论坛上的内容,确保论坛保持秩序和活跃。
主要的Api
用户App端:
帖子:创建帖子(“/create”),删除帖子(“/delete”),获取帖子(“/get”),获取所有帖子(“/getall”) 后续会考虑分页
评论:
- 创建评论:创建评论后对应帖子的评论数+1
- 删除评论:对应的评论数-1
- 获取用户被评论消息:被评论的人需要在个人中心看到评论他的消息
- 修改评论查看状态:被评论的消息被查看后,修改查看状态,消除红点
- 根据帖子id获取评论列表:点击帖子后查看帖子下的所有评论 (难点)
管理后台
帖子:获取帖子分页、审核帖子,删除帖子,置顶帖子
评论:获取评论分页、审核评论、删除评论
主要业务的实现
ForumPostServiceImpl
- 帖子审核状态的修改
- 置顶帖子(只有一个帖子置顶,因为没有记录上一个置顶帖子的id,所以置顶前需要把全部帖子设为非置顶,可能会影响效率)
- 评论后对应帖子的评论数+1(考虑到多线程问题,采用了ReentrantLock来并发控制,并且使用ConcurrentHashMap来存储每个帖子对应的锁对象,在后面的《Redis缓存点赞、点踩信息》中也有涉及)
@Service
@Validated
public class ForumPostServiceImpl implements ForumPostService {
@Resource
private ForumPostMapper forumPostMapper;
// 使用ConcurrentHashMap来存储每个帖子对应的锁对象
private final ConcurrentHashMap<Long, Lock> commentLockMap = new ConcurrentHashMap<>();
@Override
public void incCommentNumber(Long id) {
Lock commentLock = commentLockMap.computeIfAbsent(id, k -> new ReentrantLock());
commentLock.lock();
try {
LambdaUpdateWrapper<ForumPostDO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ForumPostDO::getId, id)
.setSql("comment_number = comment_number + 1");
forumPostMapper.update(null, updateWrapper);
}finally {
commentLock.unlock();
}
}
@Override
public void decCommentNumber(Long id) {
Lock commentLock = commentLockMap.computeIfAbsent(id, k -> new ReentrantLock());
commentLock.lock();
try {
LambdaUpdateWrapper<ForumPostDO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ForumPostDO::getId, id)
.setSql("comment_number = comment_number - 1");
forumPostMapper.update(null, updateWrapper);
}finally {
commentLock.unlock();
}
}
@Override
public List<ForumPostDO> getAllPostList() {
//查询审核状态 “通过” 的帖子
return forumPostMapper.selectList(ForumPostDO::getAuditStatus,"1");
}
@Override
public void updateAuditStatus(ForumPostUpdateAuditStatusReqVO reqVO) {
// 更新审核状态
ForumPostDO updateObj = new ForumPostDO()
.setId(reqVO.getId())
.setAuditStatus(reqVO.getAuditStatus())
.setAuditReason(reqVO.getAuditReason());
forumPostMapper.updateById(updateObj);
}
@Override
public void updateStickyStatus(ForumPostUpdateStickyStatusReqVO reqVO) {
if (reqVO.getSticky() == 1){
//先将所有帖子设为非置顶
LambdaUpdateWrapper<ForumPostDO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(ForumPostDO::getSticky,0);
forumPostMapper.update(null,updateWrapper);
}
ForumPostDO updateObj = new ForumPostDO()
.setId(reqVO.getId())
.setSticky(reqVO.getSticky());
forumPostMapper.updateById(updateObj);
}
}
CommentServiceImpl
- 根据帖子id获取该帖子下的所有评论
先看一下返回AppCommentRespVO的结构,主要是通过 children 来实现一个多级评论的嵌套;
AppCommentBaseVO是评论的基本信息,有帖子id,上级评论的id等
- 大致的思路:把查询出的评论列表按照上级ID分组,并转换成Map,上级评论ID为0的表示评论的是帖子,称它为0级评论,先找出0级评论,再在Map中找出上级评论ID是0级评论ID的集合,那么这个集合就是当前0级评论的子评论,然后一直递归调用。。。
public class CommentServiceImpl implements CommentService {
@Resource
private CommentMapper commentMapper;
@Override
public List<AppCommentRespVO> getCommentListByPostId(Long postId) {
LambdaQueryWrapperX<CommentDO> queryWrapper = new LambdaQueryWrapperX<>();
queryWrapper.eqIfPresent(CommentDO::getPostId,postId)
.eqIfPresent(CommentDO::getAuditStatus,"1");
List<CommentDO> commentDOList = commentMapper.selectList(queryWrapper);
// 将评论按照上级ID分组,并转换成Map
Map<Long, List<CommentDO>> commentGroupMap = commentDOList.stream().collect(Collectors.groupingBy(CommentDO::getSuperiorId));
// 将根节点下的所有评论转换成AppCommentRespVO对象
List<AppCommentRespVO> rootList = new ArrayList<>();
List<CommentDO> rootCommentDOList = commentGroupMap.get(0L);
if (rootCommentDOList != null) {
for (CommentDO rootCommentDO : rootCommentDOList) {
AppCommentRespVO root = convertToAppCommentRespVO(commentGroupMap, rootCommentDO);
rootList.add(root);
}
}
return rootList;
}
private AppCommentRespVO convertToAppCommentRespVO(Map<Long, List<CommentDO>> commentGroupMap, CommentDO commentDO) {
AppCommentRespVO appCommentRespVO = CommentConvert.INSTANCE.appConvert(commentDO);
// 获取当前评论的子评论列表
List<CommentDO> childrenCommentDOList = commentGroupMap.get(commentDO.getId());
if (childrenCommentDOList != null && !childrenCommentDOList.isEmpty()) {
List<AppCommentRespVO> children = new ArrayList<>();
for (CommentDO childCommentDO : childrenCommentDOList) {
AppCommentRespVO child = convertToAppCommentRespVO(commentGroupMap, childCommentDO);
children.add(child);
}
appCommentRespVO.setChildren(children);
}
return appCommentRespVO;
}
}