2018-11-21 更新css
感谢大佬们在评论区提的优化建议,根据建议优化了数据表的设计和代码结构。前端
新的文章:vue
评论模块优化 - 数据表优化、添加缓存及用 Feign 与用户服务通讯java
原文:git
评论模块在不少系统中都有,CodeRiver河码 做为相似程序员客栈的沟通协做平台天然也不会少。程序员
前端界面是参考了简书的评论模块,专门写了一篇文章介绍实现步骤:
vue + element-ui + scss 仿简书评论模块
感兴趣的能够看看。github
项目地址:github.com/cachecats/c…spring
代码在 根目录/java/comments-service
sql
文章将分三部分介绍:数据库
先看看前端界面长什么样,知道了前端须要什么数据,就知道数据库该怎么设计了。
首先评论的主体能够是人、项目、资源,因此要有一个 type
字段标明这条评论的类型。
以项目为例,一个项目下面可能会有多条评论。每条评论其实分为两种,一种是直接对项目的评论,称之为父评论吧;另外一种是对已有评论的评论,称为子评论。
梳理一下关系,每一个项目可能有多个父评论,每一个父评论可能有多个子评论。项目与父评论,父评论与子评论,都是一对多的关系。
由此可知数据库应该分为两个表,一个存储父评论,一个存储子评论。
再看都须要什么字段,先分析主评论。必需要有的是项目id,得知道是对谁评论的,叫 ownerId 吧。还有评论者的头像、昵称、id,还有评论时间、内容、点赞个数等。
子评论跟父评论的字段差很少,只是不要点赞数量。
分析了界面,知道须要什么字段,就开始设计数据库吧。
评论主表(父评论表)
CREATE TABLE `comments_info` (
`id` varchar(32) NOT NULL COMMENT '评论主键id',
`type` tinyint(1) NOT NULL COMMENT '评论类型:对人评论,对项目评论,对资源评论',
`owner_id` varchar(32) NOT NULL COMMENT '被评论者id,能够是人、项目、资源',
`from_id` varchar(32) NOT NULL COMMENT '评论者id',
`from_name` varchar(32) NOT NULL COMMENT '评论者名字',
`from_avatar` varchar(512) DEFAULT '' COMMENT '评论者头像',
`like_num` int(11) DEFAULT '0' COMMENT '点赞的数量',
`content` varchar(512) DEFAULT NULL COMMENT '评论内容',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `owner_id` (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论主表';
复制代码
评论回复表(子评论表)
CREATE TABLE `comments_reply` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`comment_id` varchar(32) NOT NULL COMMENT '评论主表id',
`from_id` varchar(32) NOT NULL COMMENT '评论者id',
`from_name` varchar(32) NOT NULL COMMENT '评论者名字',
`from_avatar` varchar(512) DEFAULT '' COMMENT '评论者头像',
`to_id` varchar(32) NOT NULL COMMENT '被评论者id',
`to_name` varchar(32) NOT NULL COMMENT '被评论者名字',
`to_avatar` varchar(512) DEFAULT '' COMMENT '被评论者头像',
`content` varchar(512) DEFAULT NULL COMMENT '评论内容',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `comment_id` (`comment_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='评论回复表';
复制代码
项目采用 SpringCloud
微服务架构,评论模块跟其余模块的关联性不强,能够抽出为一个单独的服务 comments-service
。
数据实体对象
CommentsInfo
package com.solo.coderiver.comments.dataobject;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;
/** * 评论表主表 */
@Entity
@Data
@DynamicUpdate
public class CommentsInfo {
//评论主键id
@Id
private String id;
//评论类型。1用户评论,2项目评论,3资源评论
private Integer type;
//被评论者的id
private String ownerId;
//评论者id
private String fromId;
//评论者名字
private String fromName;
//评论者头像
private String fromAvatar;
//得到点赞的数量
private Integer likeNum;
//评论内容
private String content;
//建立时间
private Date createTime;
//更新时间
private Date updateTime;
}
复制代码
数据实体对象
CommentsReply
package com.solo.coderiver.comments.dataobject;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;
/** * 评论回复表 */
@Entity
@Data
@DynamicUpdate
public class CommentsReply {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//评论主表id
private String commentId;
//评论者id
private String fromId;
//评论者名字
private String fromName;
//评论者头像
private String fromAvatar;
//被评论者id
private String toId;
//被评论者名字
private String toName;
//被评论者头像
private String toAvatar;
//评论内容
private String content;
//建立时间
private Date createTime;
//更新时间
private Date updateTime;
}
复制代码
操做数据库暂时用的是 Jpa
,后期可能会增长一份 mybatis
的实现。
CommentsInfoRepository
package com.solo.coderiver.comments.repository;
import com.solo.coderiver.comments.dataobject.CommentsInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CommentsInfoRepository extends JpaRepository<CommentsInfo, String> {
List<CommentsInfo> findByOwnerId(String ownerId);
}
复制代码
CommentsReplyRepository
package com.solo.coderiver.comments.repository;
import com.solo.coderiver.comments.dataobject.CommentsReply;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CommentsReplyRepository extends JpaRepository<CommentsReply, Integer> {
List<CommentsReply> findByCommentId(String commentId);
}
复制代码
为了代码更健壮,要把数据库的操做封装一下
CommentsInfoService
package com.solo.coderiver.comments.service;
import com.solo.coderiver.comments.dataobject.CommentsInfo;
import java.util.List;
public interface CommentsInfoService {
/** * 保存评论 * @param info * @return */
CommentsInfo save(CommentsInfo info);
/** * 根据被评论者的id查询评论列表 * @param ownerId * @return */
List<CommentsInfo> findByOwnerId(String ownerId);
}
复制代码
CommentsReplyService
package com.solo.coderiver.comments.service;
import com.solo.coderiver.comments.dataobject.CommentsReply;
import java.util.List;
public interface CommentsReplyService {
/** * 保存评论回复 * @param reply * @return */
CommentsReply save(CommentsReply reply);
/** * 根据评论id查询回复 * @param commentId * @return */
List<CommentsReply> findByCommentId(String commentId);
}
复制代码
接口的实现类
CommentsInfoServiceImpl
package com.solo.coderiver.comments.service.impl;
import com.solo.coderiver.comments.dataobject.CommentsInfo;
import com.solo.coderiver.comments.repository.CommentsInfoRepository;
import com.solo.coderiver.comments.service.CommentsInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentsInfoServiceImpl implements CommentsInfoService {
@Autowired
CommentsInfoRepository repository;
@Override
public CommentsInfo save(CommentsInfo info) {
return repository.save(info);
}
@Override
public List<CommentsInfo> findByOwnerId(String ownerId) {
return repository.findByOwnerId(ownerId);
}
}
复制代码
CommentsReplyServiceImpl
package com.solo.coderiver.comments.service.impl;
import com.solo.coderiver.comments.dataobject.CommentsReply;
import com.solo.coderiver.comments.repository.CommentsReplyRepository;
import com.solo.coderiver.comments.service.CommentsReplyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentsReplyServiceImpl implements CommentsReplyService {
@Autowired
CommentsReplyRepository repository;
@Override
public CommentsReply save(CommentsReply reply) {
return repository.save(reply);
}
@Override
public List<CommentsReply> findByCommentId(String commentId) {
return repository.findByCommentId(commentId);
}
}
复制代码
Controller
层分发请求,并返回前端须要的数据
package com.solo.coderiver.comments.controller;
@RestController
@Api(description = "评论相关接口")
public class CommentsController {
@Autowired
CommentsInfoService infoService;
@Autowired
CommentsReplyService replyService;
@PostMapping("/save")
@ApiOperation("保存评论")
@Transactional
public ResultVO saveComments(@Valid CommentsInfoForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new CommentsException(ResultEnums.PARAMS_ERROR.getCode(), bindingResult.getFieldError().getDefaultMessage());
}
//将 CommentsInfoForm 里的数据拷贝到 CommentsInfo
CommentsInfo info = new CommentsInfo();
BeanUtils.copyProperties(form, info);
// 生成并设置评论的主键id
info.setId(KeyUtils.genUniqueKey());
CommentsInfo result = infoService.save(info);
if (result == null) {
throw new CommentsException(ResultEnums.SAVE_COMMENTS_FAIL);
}
return ResultVOUtils.success();
}
@GetMapping("/get/{ownerId}")
@ApiOperation("根据 ownerId 查询评论")
@ApiImplicitParam(name = "ownerId", value = "被评论者id")
public ResultVO getCommentsByOwnerId(@PathVariable("ownerId") String ownerId) {
List<CommentsInfo> infoList = infoService.findByOwnerId(ownerId);
//将 CommentsInfo 转换为 CommentsInfoDTO
List<CommentsInfoDTO> infoDTOS = infoList.stream().map(info -> {
CommentsInfoDTO dto = new CommentsInfoDTO();
BeanUtils.copyProperties(info, dto);
return dto;
}).collect(Collectors.toList());
return ResultVOUtils.success(infoDTOS);
}
@PostMapping("/save-reply")
@ApiOperation("保存评论回复")
@Transactional
public ResultVO saveReply(@Valid CommentsReplyForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new CommentsException(ResultEnums.PARAMS_ERROR.getCode(), bindingResult.getFieldError().getDefaultMessage());
}
CommentsReply reply = new CommentsReply();
BeanUtils.copyProperties(form, reply);
CommentsReply result = replyService.save(reply);
if (result == null) {
throw new CommentsException(ResultEnums.SAVE_COMMENTS_FAIL);
}
return ResultVOUtils.success();
}
@GetMapping("/get-reply/{commentId}")
@ApiOperation("经过commentId获取评论回复")
public ResultVO getReplyByCommentId(@PathVariable("commentId") String commentId) {
List<CommentsReply> replyList = replyService.findByCommentId(commentId);
//将 CommentsReply 转换为 CommentsReplyDTO
List<CommentsReplyDTO> replyDTOS = replyList.stream().map(reply -> {
CommentsReplyDTO dto = new CommentsReplyDTO();
BeanUtils.copyProperties(reply, dto);
return dto;
}).collect(Collectors.toList());
return ResultVOUtils.success(replyDTOS);
}
}
复制代码
代码中工具类和枚举类请到 github
上查看源码。
以上就是对评论模块的设计与功能实现,欢迎各位大佬提出代码优化建议,共同成长~
代码出自开源项目 CodeRiver
,致力于打造全平台型全栈精品开源项目。
coderiver 中文名 河码,是一个为程序员和设计师提供项目协做的平台。不管你是前端、后端、移动端开发人员,或是设计师、产品经理,均可以在平台上发布项目,与志同道合的小伙伴一块儿协做完成项目。
coderiver河码 相似程序员客栈,但主要目的是方便各细分领域人才之间技术交流,共同成长,多人协做完成项目。暂不涉及金钱交易。
计划作成包含 pc端(Vue、React)、移动H5(Vue、React)、ReactNative混合开发、Android原生、微信小程序、java后端的全平台型全栈项目,欢迎关注。
您的鼓励是我前行最大的动力,欢迎点赞,欢迎送小星星✨ ~