評論模組 - 後端資料庫設計及功能實現

solocoder發表於2018-11-07

2018-11-21 更新

感謝大佬們在評論區提的優化建議,根據建議優化了資料表的設計和程式碼結構。

新的文章:

評論模組優化 - 資料表優化、新增快取及用 Feign 與使用者服務通訊


原文:

評論模組在很多系統中都有,CodeRiver河碼 作為類似程式設計師客棧的溝通協作平臺自然也不會少。

前端介面是參考了簡書的評論模組,專門寫了一篇文章介紹實現步驟:
vue + element-ui + scss 仿簡書評論模組
感興趣的可以看看。

專案地址:github.com/cachecats/c…

程式碼在 根目錄/java/comments-service

文章將分三部分介紹:

  1. 前端介面分析
  2. 資料庫設計
  3. 功能實現

一、前端介面分析

先看看前端介面長什麼樣,知道了前端需要什麼資料,就知道資料庫該怎麼設計了。

評論模組 - 後端資料庫設計及功能實現

首先評論的主體可以是人、專案、資源,所以要有一個 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後端的全平臺型全棧專案,歡迎關注。

專案地址:github.com/cachecats/c…


您的鼓勵是我前行最大的動力,歡迎點贊,歡迎送小星星✨ ~

相關文章