2018-11-21 更新
感謝大佬們在評論區提的優化建議,根據建議優化了資料表的設計和程式碼結構。
新的文章:
評論模組優化 - 資料表優化、新增快取及用 Feign 與使用者服務通訊
原文:
評論模組在很多系統中都有,CodeRiver河碼 作為類似程式設計師客棧的溝通協作平臺自然也不會少。
前端介面是參考了簡書的評論模組,專門寫了一篇文章介紹實現步驟:
vue + element-ui + scss 仿簡書評論模組
感興趣的可以看看。
程式碼在 根目錄/java/comments-service
文章將分三部分介紹:
- 前端介面分析
- 資料庫設計
- 功能實現
一、前端介面分析
先看看前端介面長什麼樣,知道了前端需要什麼資料,就知道資料庫該怎麼設計了。
首先評論的主體可以是人、專案、資源,所以要有一個 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;
}
複製程式碼
資料庫操作倉庫 repository
運算元據庫暫時用的是 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);
}
複製程式碼
Service 介面封裝
為了程式碼更健壯,要把資料庫的操作封裝一下
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
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後端的全平臺型全棧專案,歡迎關注。
您的鼓勵是我前行最大的動力,歡迎點贊,歡迎送小星星✨ ~