基于MongoDB文章评论实现与设计
前提:本文旨在学习mongoDB对系统评论功能的实现思路,并不是完整的实现系统的评论功能。
设计
评论集合的结构
_id | mongoDB文档默认编号 |
---|---|
cid | 文章id,有雪花算法生成 |
content | 评论内容 |
publishdate | 评论发布时间 |
userId | 评论人 |
articleId | 评论所属文章ID |
thumbup | 评论被点赞数 |
parentId | 0表示评论文章;若是评论的是评论则为被评论的评论c_id |
思路
用户能对文章进行评论也能对评论进行评论,类似于树形结构。所以存在评论父级ID:parentId,当用户评论的是文章时,parentId=0;当用户评论的是别人的评论是,parentId = 被评论数据的ID
文章的评论是一次性查出来好,还是单个查出来的好?
单个查询好一些。在文章页面中,先只查询属于文章的评论,也就是parentId=0的评论数据。因为,评论的评论并不是所有的人都想看,若想看,想看的点开单独查询即可(使用异步加载),且全部查出来的方式递归效率低,可能导致系统性能差。
从系统性能与用户体验考虑,单个查询较好。
操作mongoDB的技术
使用Spring-Data-MongoDB
实现
导入依赖
<!--spring data mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
本人将mongodb部署在docker容器中
ip:192.168.200.135
端口为默认端口:27017
配置文件配置
spring:
data:
mongodb:
host: 192.168.200.135 #连接ip
port: 27017 #端口号
database: commentDB #数据库
创建文章评论pojo实体类
package cn.jankin.pojo;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
import java.util.Date;
/**
* 评论实体
*/
@Document("comment")
public class Comment implements Serializable {
@Id
private String _id; //mongodb默认主键,加快数据的插入
private String cid; //雪花生成器生成评论标识,mongoDB建立索引
private String articleid;
private String content;
private String userid;
private String parentid;
private Date publishdate;
private Integer thumbup;
}
注意:在这里我并没有将mongodb默认的 _id 作为评论的标识,而是分配另一个属性 cid作为标识。
原因:优化mongodb的性能,解决可能发生的数据不一致问题。
意义:系统内每一分每一秒都有可能有用户发布评论,若系统采用默认的 _id作为评论标识,我们知道,mongodb默认的 _id的类型为ObjectId,这是一个12byte组成的数据,分别由时间戳、机器标识码、mongoDB实例进程号以及随机序列组成。若是搭建了mongoDB的集群环境,在大量用户发布评论的情况下,生成的ObjectId是有可能相同的,这就导致了评论的唯一性被打破。
那么,我们也可以不使用mongodb默认的ObjectId,系统程序生成一个唯一标识设置为 _id ,例如雪花算法生成的id,不是吗?
是的,但是mongodb中集合是存在 _id 索引的,通过索引mongodb才能够快速的查询出指定的文档,在插入使,若使用自定义值的 _id, 那么将极大的影响mongodb文档的插入效率,特别是在集合中现存文档的数量极其庞大时尤为明显,所以上述做法会影响系统后期时的性能,也不建议使用。
退而求其次,不使用mongoDB默认的唯一标识不就好了,故cid诞生了。cid是由雪花算法生成的id,保证了评论的唯一性,而评论的_id 仍然使用默认类型,保证插入的高效性。而我们只需要另外建一个mongodb中cid索引就也能保证通过cid查询时的性能问题,这就是典型的以空间换时间的概念。
搭建项目的过程就不再赘述了。直接开始敲代码吧。
分析评论所需要的功能
1、通过评论cid查询评论
2、通过文章id查询评论,顶级评论(评论文章的评论),按发布时间排序
3、通过文章id查询某一评论下的评论
3、通过用户id查询评论,按发布时间排序
4、根据id删除评论
5、评论点赞与取消点赞
6、发布评论
注意,评论我设置为没有修改评论功能,如果想改,就只能删除后进行重新发布评论。
对于遗漏的功能,通过上述的功能实现,基本也就能依葫芦画瓢了。
对于数据的参数校验问题,我使用的是hibernate validator,你们可以采用别的方法,这不属于本文内容。
采用restful来实现前后端分离。
我们只进行后端实现。
操作mongoDB的方式我采用了两种:
一种是使用MongoDBTemplate实现类似于需要用到列值增长等函数的操作;
一种是创建持久化接口CommentRepository 继承松日那个 data mongodb 提供的MongoDBRepository接口,进行方法拼接实现简单的条件查询。
这里我只给出操作mongoDB获取数据的方法,对于三层架构和spring boot的操作在代码中不体现。
开始敲代码
直接从service层开始吧,表现层就不体现了。
创建Service接口
package cn.jankin.service;
import cn.jankin.pojo.Comment;
import java.util.List;
import java.util.Map;
/**
* 评论业务接口
*/
public interface CommentService {
/**
* 通过评论id获取评论数据
* @param id 评论id
* @return
*/
Comment findById(String id);
/**
* 根据用户id查找用户所发布的评论,并按照发布时间排序
* @param userId
* @param sortType
* @return
*/
List<Comment> findByUserIdOrderByPublishdate(String userId,String sortType);
/**
* 查询所属指定文章的评论,不包含评论的评论,并按评论发布时间排序
* @param articleId
* @param sortType
* @return
*/
List<Comment> findByArticleIdOrderByPublishdate(String articleId,String sortType);
/**
* 通过评论id修改评论数据
* @param comment
*/
void updateById(Comment comment);
/**
* 通过评论id删除评论
* @param commentId
*/
void deleteById(String commentId);
/**
* 点赞评论功能实现
* @param commentId 被点赞评论id
* @param isThumbup true : 点赞 ; false : 取消点赞
*/
void thumbup(String commentId,Boolean isThumbup);
/**
* 发布评论
* @param comment
*/
void save(Comment comment);
}
实现功能一:根据评论id查询评论findById。
service
/**
* 通过评论标识c_id查询评论数据
* @param id 评论id
* @return
*/
@Override
public Comment findById(String id) {
Comment comment = commentRepositoty.findByCid(id);
return comment;
}
repository:采用方法条件拼接实现
/**
* 通过评论c_id查询评论数据
* @param cid
* @return
*/
Comment findByCid(String cid);
实现功能二:通过文章id查询评论,顶级评论(评论文章的评论),按发布时间排序
service
/**
* 根据文章id查询评论,并按时间排序
* @param articleId
* @param sortType
* @return
*/
@Override
public List<Comment> findByArticleIdOrderByPublishdate(String articleId, String sortType) {
List<Comment> list = null;
//判断排序规则
if(sortType.equals(SortType.ASC)){
list = commentRepositoty.findByArticleidOrderByPublishdateAsc(articleId);
}else{
list = commentRepositoty.findByArticleidOrderByPublishdateDesc(articleId);
}
return list;
}
在这里有一个sortType参数,是用来标识是升序排序还是降序排序。
package cn.jankin.entity;
/**
* 排序类型
*/
public class SortType {
public final static String ASC = "1"; //升序
public final static String DESC = "-1"; //降序
}
这样在我们需要改变前端排序参数时,直接该这个类就行,而不需要一个类一个类的改。设计原则:对修改封闭,对扩展开放。
repository
/**
* 根据文章id查询评论,并根据发布时间正序排序
* @param articleId
* @return
*/
List<Comment> findByArticleidOrderByPublishdateAsc(String articleId);
/**
* 根据文章id查询评论,并根据发布时间倒叙排序
* @param articleId
* @return
*/
List<Comment> findByArticleidOrderByPublishdateDesc(String articleId);
实现功能三:通过用户id查询评论,按发布时间排序
因为与文章id查询做法相差不多,不再赘述,但是要考虑登录用户的问题,我这只是demo,没有实现用户登录功能。
实现功能四:根据cid删除评论
service
/**
* 删除指定评论id的评论文档
* @param commentId
*/
@Override
public void deleteById(String commentId) {
commentRepositoty.deleteByCid(commentId);
}
repository
/**
* 通过用户标识c_id删除评论
* @param cid
*/
void deleteByCid(String cid);
实现功能五:评论点赞与取消点赞
/**
* 用户点赞与取消点赞评论根据评论id
* @param commentId 被点赞评论id
* @param isThumbup true : 点赞 ; false : 取消点赞
*/
@Override
public void thumbup(String commentId, Boolean isThumbup) {
Query query = new Query(Criteria.where("cid").is(commentId)); //设置修改条件
Update update = new Update();
if(isThumbup){ //点赞操作
update.inc("thumbup",1);
}else{ //取消点赞操作
update.inc("thumbup",-1);
}
mongoTemplate.updateFirst(query,update,"comment");
}
实现功能六:发布评论
service
/**
* 发布评论
* @param comment
*/
@Override
public void save(Comment comment) {
//生成id
String id = idWorker.nextId()+"";
comment.setCid(id);
//设置发布时间
comment.setPublishdate(new Date());
//设置初始点赞数
comment.setThumbup(0);
//保存
commentRepositoty.save(comment);
}