摘要:前段時間設(shè)計了系統(tǒng)的評論模塊,并寫了篇文章評論模塊后端數(shù)據(jù)庫設(shè)計及功能實現(xiàn)講解。下面就對評論模塊進行優(yōu)化改造,首先更改表結(jié)構(gòu),合成一張表。評論表不存用戶頭像的話,需要從用戶服務(wù)獲取。用戶服務(wù)提供獲取頭像的接口,兩個服務(wù)間通過通信。
前段時間設(shè)計了系統(tǒng)的評論模塊,并寫了篇文章 評論模塊 - 后端數(shù)據(jù)庫設(shè)計及功能實現(xiàn) 講解。
大佬們在評論區(qū)提出了些優(yōu)化建議,總結(jié)一下:
之前評論一共分了兩張表,一個評論主表,一個回復表。這兩張表的字段區(qū)別不大,在主表上加個 pid 字段就可以不用回復表合成一張表了。
評論表中存了用戶頭像,會引發(fā)一些問題。比如用戶換頭像時要把評論也一起更新不太合適,還可能出現(xiàn)兩條評論頭像不一致的情況。
的確數(shù)據(jù)庫設(shè)計的有問題,感謝 wangbjun 和 JWang。
下面就對評論模塊進行優(yōu)化改造,首先更改表結(jié)構(gòu),合成一張表。評論表不存用戶頭像的話,需要從用戶服務(wù)獲取。用戶服務(wù)提供獲取頭像的接口,兩個服務(wù)間通過 Feign 通信。
這樣有個問題,如果一個資源的評論比較多,每個評論都調(diào)用用戶服務(wù)查詢頭像還是有點慢,所以對評論查詢加個 Redis 緩存。要是有新的評論,就把這個資源緩存的評論刪除,下次請求時重新讀數(shù)據(jù)庫并將最新的數(shù)據(jù)緩存到 Redis 中。
代碼出自開源項目 coderiver,致力于打造全平臺型全棧精品開源項目。
項目地址:https://github.com/cachecats/...
本文將分四部分介紹
數(shù)據(jù)庫改造
用戶服務(wù)提供獲取頭像接口
評論服務(wù)用 Feign 訪問用戶服務(wù)取頭像
使用 Redis 緩存數(shù)據(jù)
一、數(shù)據(jù)庫改造數(shù)據(jù)庫表重新設(shè)計如下
CREATE TABLE `comments_info` ( `id` varchar(32) NOT NULL COMMENT "評論主鍵id", `pid` varchar(32) DEFAULT "" COMMENT "父評論id", `owner_id` varchar(32) NOT NULL COMMENT "被評論的資源id,可以是人、項目、資源", `type` tinyint(1) NOT NULL COMMENT "評論類型:對人評論,對項目評論,對資源評論", `from_id` varchar(32) NOT NULL COMMENT "評論者id", `from_name` varchar(32) NOT NULL COMMENT "評論者名字", `to_id` varchar(32) DEFAULT "" COMMENT "被評論者id", `to_name` varchar(32) DEFAULT "" COMMENT "被評論者名字", `like_num` int(11) DEFAULT "0" COMMENT "點贊的數(shù)量", `content` varchar(512) DEFAULT NULL COMMENT "評論內(nèi)容", `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT "創(chuàng)建時間", `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="評論表";
相比之前添加了父評論id pid ,去掉了用戶頭像。owner_id 是被評論的資源id,比如一個項目下的所有評論的 owner_id 都是一樣的,便于根據(jù)資源 id 查找該資源下的所有評論。
與數(shù)據(jù)表對應(yīng)的實體類 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.io.Serializable; import java.util.Date; /** * 評論表主表 */ @Entity @Data @DynamicUpdate public class CommentsInfo implements Serializable{ private static final long serialVersionUID = -4568928073579442976L; //評論主鍵id @Id private String id; //該條評論的父評論id private String pid; //評論的資源id。標記這條評論是屬于哪個資源的。資源可以是人、項目、設(shè)計資源 private String ownerId; //評論類型。1用戶評論,2項目評論,3資源評論 private Integer type; //評論者id private String fromId; //評論者名字 private String fromName; //被評論者id private String toId; //被評論者名字 private String toName; //獲得點贊的數(shù)量 private Integer likeNum; //評論內(nèi)容 private String content; //創(chuàng)建時間 private Date createTime; //更新時間 private Date updateTime; }
數(shù)據(jù)傳輸對象 CommentsInfoDTO
在 DTO 對象中添加了用戶頭像,和子評論列表 children,因為返給前端要有層級嵌套。
package com.solo.coderiver.comments.dto; import lombok.Data; import java.io.Serializable; import java.util.Date; import java.util.List; @Data public class CommentsInfoDTO implements Serializable { private static final long serialVersionUID = -6788130126931979110L; //評論主鍵id private String id; //該條評論的父評論id private String pid; //評論的資源id。標記這條評論是屬于哪個資源的。資源可以是人、項目、設(shè)計資源 private String ownerId; //評論類型。1用戶評論,2項目評論,3資源評論 private Integer type; //評論者id private String fromId; //評論者名字 private String fromName; //評論者頭像 private String fromAvatar; //被評論者id private String toId; //被評論者名字 private String toName; //被評論者頭像 private String toAvatar; //獲得點贊的數(shù)量 private Integer likeNum; //評論內(nèi)容 private String content; //創(chuàng)建時間 private Date createTime; //更新時間 private Date updateTime; private List二、用戶服務(wù)提供獲取頭像接口children; }
為了方便理解先看一下項目的結(jié)構(gòu),本項目中所有的服務(wù)都是這種結(jié)構(gòu)
每個服務(wù)都分為三個 Module,分別是 client , common , server。
client :為其他服務(wù)提供數(shù)據(jù),F(xiàn)eign 的接口就寫在這層。
common :放 client 和 server 公用的代碼,比如公用的對象、工具類。
server : 主要的邏輯代碼。
在 client 的 pom.xml 中引入 Feign 的依賴
org.springframework.cloud spring-cloud-starter-openfeign
用戶服務(wù) user 需要對外暴露獲取用戶頭像的接口,以使評論服務(wù)通過 Feign 調(diào)用。
在 user_service 項目的 server 下新建 ClientController , 提供獲取頭像的接口。
package com.solo.coderiver.user.controller; import com.solo.coderiver.user.common.UserInfoForComments; import com.solo.coderiver.user.dataobject.UserInfo; import com.solo.coderiver.user.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 對其他服務(wù)提供數(shù)據(jù)的 controller */ @RestController @Slf4j public class ClientController { @Autowired UserService userService; /** * 通過 userId 獲取用戶頭像 * * @param userId * @return */ @GetMapping("/get-avatar") public UserInfoForComments getAvatarByUserId(@RequestParam("userId") String userId) { UserInfo info = userService.findById(userId); if (info == null){ return null; } return new UserInfoForComments(info.getId(), info.getAvatar()); } }
然后在 client 定義 UserClient 接口
package com.solo.coderiver.user.client; import com.solo.coderiver.user.common.UserInfoForComments; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "user") public interface UserClient { @GetMapping("/user/get-avatar") UserInfoForComments getAvatarByUserId(@RequestParam("userId") String userId); }三、評論服務(wù)用 Feign 訪問用戶服務(wù)取頭像
在評論服務(wù)的 server 層的 pom.xml 里添加 Feign 依賴
org.springframework.cloud spring-cloud-starter-openfeign
并在入口類添加注解 @EnableFeignClients(basePackages = "com.solo.coderiver.user.client") 注意到配置掃描包的全類名
package com.solo.coderiver.comments; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import springfox.documentation.swagger2.annotations.EnableSwagger2; @SpringBootApplication @EnableDiscoveryClient @EnableSwagger2 @EnableFeignClients(basePackages = "com.solo.coderiver.user.client") @EnableCaching public class CommentsApplication { public static void main(String[] args) { SpringApplication.run(CommentsApplication.class, args); } }
封裝 CommentsInfoService ,提供保存評論和獲取評論的接口
package com.solo.coderiver.comments.service; import com.solo.coderiver.comments.dto.CommentsInfoDTO; import java.util.List; public interface CommentsInfoService { /** * 保存評論 * * @param info * @return */ CommentsInfoDTO save(CommentsInfoDTO info); /** * 根據(jù)被評論的資源id查詢評論列表 * * @param ownerId * @return */ ListfindByOwnerId(String ownerId); }
CommentsInfoService 的實現(xiàn)類
package com.solo.coderiver.comments.service.impl; import com.solo.coderiver.comments.converter.CommentsConverter; import com.solo.coderiver.comments.dataobject.CommentsInfo; import com.solo.coderiver.comments.dto.CommentsInfoDTO; import com.solo.coderiver.comments.repository.CommentsInfoRepository; import com.solo.coderiver.comments.service.CommentsInfoService; import com.solo.coderiver.user.client.UserClient; import com.solo.coderiver.user.common.UserInfoForComments; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service @Slf4j public class CommentsInfoServiceImpl implements CommentsInfoService { @Autowired CommentsInfoRepository repository; @Autowired UserClient userClient; @Override @CacheEvict(cacheNames = "comments", key = "#dto.ownerId") public CommentsInfoDTO save(CommentsInfoDTO dto) { CommentsInfo result = repository.save(CommentsConverter.DTO2Info(dto)); return CommentsConverter.info2DTO(result); } @Override @Cacheable(cacheNames = "comments", key = "#ownerId") public ListfindByOwnerId(String ownerId) { List infoList = repository.findByOwnerId(ownerId); List list = CommentsConverter.infos2DTOList(infoList) .stream() .map(dto -> { //從用戶服務(wù)取評論者頭像 UserInfoForComments fromUser = userClient.getAvatarByUserId(dto.getFromId()); if (fromUser != null) { dto.setFromAvatar(fromUser.getAvatar()); } //從用戶服務(wù)取被評論者頭像 String toId = dto.getToId(); if (!StringUtils.isEmpty(toId)) { UserInfoForComments toUser = userClient.getAvatarByUserId(toId); if (toUser != null) { dto.setToAvatar(toUser.getAvatar()); } } return dto; }).collect(Collectors.toList()); return sortData(list); } /** * 將無序的數(shù)據(jù)整理成有層級關(guān)系的數(shù)據(jù) * * @param dtos * @return */ private List sortData(List dtos) { List list = new ArrayList<>(); for (int i = 0; i < dtos.size(); i++) { CommentsInfoDTO dto1 = dtos.get(i); List children = new ArrayList<>(); for (int j = 0; j < dtos.size(); j++) { CommentsInfoDTO dto2 = dtos.get(j); if (dto2.getPid() == null) { continue; } if (dto1.getId().equals(dto2.getPid())) { children.add(dto2); } } dto1.setChildren(children); //最外層的數(shù)據(jù)只添加 pid 為空的評論,其他評論在父評論的 children 下 if (dto1.getPid() == null || StringUtils.isEmpty(dto1.getPid())) { list.add(dto1); } } return list; } }
從數(shù)據(jù)庫取出來的評論是無序的,為了方便前端展示,需要對評論按層級排序,子評論在父評論的 children 字段中。
返回的數(shù)據(jù):
{ "code": 0, "msg": "success", "data": [ { "id": "1542338175424142145", "pid": null, "ownerId": "1541062468073593543", "type": 1, "fromId": "555555", "fromName": "張揚", "fromAvatar": null, "toId": null, "toName": null, "toAvatar": null, "likeNum": 0, "content": "你好呀", "createTime": "2018-11-16T03:16:15.000+0000", "updateTime": "2018-11-16T03:16:15.000+0000", "children": [] }, { "id": "1542338522933315867", "pid": null, "ownerId": "1541062468073593543", "type": 1, "fromId": "555555", "fromName": "張揚", "fromAvatar": null, "toId": null, "toName": null, "toAvatar": null, "likeNum": 0, "content": "你好呀嘿嘿", "createTime": "2018-11-16T03:22:03.000+0000", "updateTime": "2018-11-16T03:22:03.000+0000", "children": [] }, { "id": "abc123", "pid": null, "ownerId": "1541062468073593543", "type": 1, "fromId": "333333", "fromName": "王五", "fromAvatar": "http://avatar.png", "toId": null, "toName": null, "toAvatar": null, "likeNum": 3, "content": "這個小伙子不錯", "createTime": "2018-11-15T06:06:10.000+0000", "updateTime": "2018-11-15T06:06:10.000+0000", "children": [ { "id": "abc456", "pid": "abc123", "ownerId": "1541062468073593543", "type": 1, "fromId": "222222", "fromName": "李四", "fromAvatar": "http://222.png", "toId": "abc123", "toName": "王五", "toAvatar": null, "likeNum": 2, "content": "這個小伙子不錯啊啊啊啊啊", "createTime": "2018-11-15T06:08:18.000+0000", "updateTime": "2018-11-15T06:36:47.000+0000", "children": [] } ] } ] }四、使用 Redis 緩存數(shù)據(jù)
其實緩存已經(jīng)在上面的代碼中做過了,兩個方法上的
@Cacheable(cacheNames = "comments", key = "#ownerId") @CacheEvict(cacheNames = "comments", key = "#dto.ownerId")
兩個注解就搞定了。第一次請求接口會走方法體
關(guān)于 Redis 的使用方法,我專門寫了篇文章介紹,就不在這里多說了,需要的可以看看這篇文章:
Redis詳解 - SpringBoot整合Redis,RedisTemplate和注解兩種方式的使用
以上就是對評論模塊的優(yōu)化,歡迎大佬們提優(yōu)化建議~
代碼出自開源項目 coderiver,致力于打造全平臺型全棧精品開源項目。
coderiver 中文名 河碼,是一個為程序員和設(shè)計師提供項目協(xié)作的平臺。無論你是前端、后端、移動端開發(fā)人員,或是設(shè)計師、產(chǎn)品經(jīng)理,都可以在平臺上發(fā)布項目,與志同道合的小伙伴一起協(xié)作完成項目。
coderiver河碼 類似程序員客棧,但主要目的是方便各細分領(lǐng)域人才之間技術(shù)交流,共同成長,多人協(xié)作完成項目。暫不涉及金錢交易。
計劃做成包含 pc端(Vue、React)、移動H5(Vue、React)、ReactNative混合開發(fā)、Android原生、微信小程序、java后端的全平臺型全棧項目,歡迎關(guān)注。
項目地址:https://github.com/cachecats/...
您的鼓勵是我前行最大的動力,歡迎點贊,歡迎送小星星? ~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/72272.html
摘要:無論你是前端后端移動端開發(fā)人員,或是設(shè)計師產(chǎn)品經(jīng)理,都可以在平臺上發(fā)布項目,與志同道合的小伙伴一起協(xié)作完成項目。 全平臺全棧開源項目 coderiver 今天終于開始前后端聯(lián)調(diào)了~ 首先感謝大家的支持,coderiver 在 GitHub 上開源兩周,獲得了 54 個 Star,9 個 Fork,5 個 Watch。 這些鼓勵和認可也更加堅定了我繼續(xù)寫下去的決心~ 再次感謝各位大佬! ...
摘要:是一個相對比較新的微服務(wù)框架,年才推出的版本雖然時間最短但是相比等框架提供的全套的分布式系統(tǒng)解決方案。提供線程池不同的服務(wù)走不同的線程池,實現(xiàn)了不同服務(wù)調(diào)用的隔離,避免了服務(wù)器雪崩的問題。通過互相注冊的方式來進行消息同步和保證高可用。 Spring Cloud 是一個相對比較新的微服務(wù)框架,...
摘要:集群系統(tǒng)中的單個計算機通常稱為節(jié)點,通常通過局域網(wǎng)連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負責了。于是,小周和就變成了協(xié)作開發(fā)。都是為了項目正常運行以及迭代。 一、前言 只有光頭才能變強 認識我的朋友可能都知道我這陣子去實習啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
摘要:集群系統(tǒng)中的單個計算機通常稱為節(jié)點,通常通過局域網(wǎng)連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負責了。于是,小周和就變成了協(xié)作開發(fā)。都是為了項目正常運行以及迭代。 一、前言 只有光頭才能變強 認識我的朋友可能都知道我這陣子去實習啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
閱讀 1784·2023-04-25 21:50
閱讀 2429·2019-08-30 15:53
閱讀 774·2019-08-30 13:19
閱讀 2752·2019-08-28 17:58
閱讀 2476·2019-08-23 16:21
閱讀 2709·2019-08-23 14:08
閱讀 1384·2019-08-23 11:32
閱讀 1448·2019-08-22 16:09