摘要:前段時間設計了系統的評論模塊,并寫了篇文章評論模塊后端數據庫設計及功能實現講解。下面就對評論模塊進行優化改造,首先更改表結構,合成一張表。評論表不存用戶頭像的話,需要從用戶服務獲取。用戶服務提供獲取頭像的接口,兩個服務間通過通信。
前段時間設計了系統的評論模塊,并寫了篇文章 評論模塊 - 后端數據庫設計及功能實現 講解。
大佬們在評論區提出了些優化建議,總結一下:
之前評論一共分了兩張表,一個評論主表,一個回復表。這兩張表的字段區別不大,在主表上加個 pid 字段就可以不用回復表合成一張表了。
評論表中存了用戶頭像,會引發一些問題。比如用戶換頭像時要把評論也一起更新不太合適,還可能出現兩條評論頭像不一致的情況。
的確數據庫設計的有問題,感謝 wangbjun 和 JWang。
下面就對評論模塊進行優化改造,首先更改表結構,合成一張表。評論表不存用戶頭像的話,需要從用戶服務獲取。用戶服務提供獲取頭像的接口,兩個服務間通過 Feign 通信。
這樣有個問題,如果一個資源的評論比較多,每個評論都調用用戶服務查詢頭像還是有點慢,所以對評論查詢加個 Redis 緩存。要是有新的評論,就把這個資源緩存的評論刪除,下次請求時重新讀數據庫并將最新的數據緩存到 Redis 中。
代碼出自開源項目 coderiver,致力于打造全平臺型全棧精品開源項目。
項目地址:https://github.com/cachecats/...
本文將分四部分介紹
數據庫改造
用戶服務提供獲取頭像接口
評論服務用 Feign 訪問用戶服務取頭像
使用 Redis 緩存數據
一、數據庫改造數據庫表重新設計如下
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 "點贊的數量", `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="評論表";
相比之前添加了父評論id pid ,去掉了用戶頭像。owner_id 是被評論的資源id,比如一個項目下的所有評論的 owner_id 都是一樣的,便于根據資源 id 查找該資源下的所有評論。
與數據表對應的實體類 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。標記這條評論是屬于哪個資源的。資源可以是人、項目、設計資源 private String ownerId; //評論類型。1用戶評論,2項目評論,3資源評論 private Integer type; //評論者id private String fromId; //評論者名字 private String fromName; //被評論者id private String toId; //被評論者名字 private String toName; //獲得點贊的數量 private Integer likeNum; //評論內容 private String content; //創建時間 private Date createTime; //更新時間 private Date updateTime; }
數據傳輸對象 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。標記這條評論是屬于哪個資源的。資源可以是人、項目、設計資源 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; //獲得點贊的數量 private Integer likeNum; //評論內容 private String content; //創建時間 private Date createTime; //更新時間 private Date updateTime; private List二、用戶服務提供獲取頭像接口children; }
為了方便理解先看一下項目的結構,本項目中所有的服務都是這種結構
每個服務都分為三個 Module,分別是 client , common , server。
client :為其他服務提供數據,Feign 的接口就寫在這層。
common :放 client 和 server 公用的代碼,比如公用的對象、工具類。
server : 主要的邏輯代碼。
在 client 的 pom.xml 中引入 Feign 的依賴
org.springframework.cloud spring-cloud-starter-openfeign
用戶服務 user 需要對外暴露獲取用戶頭像的接口,以使評論服務通過 Feign 調用。
在 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; /** * 對其他服務提供數據的 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); }三、評論服務用 Feign 訪問用戶服務取頭像
在評論服務的 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); /** * 根據被評論的資源id查詢評論列表 * * @param ownerId * @return */ ListfindByOwnerId(String ownerId); }
CommentsInfoService 的實現類
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 -> { //從用戶服務取評論者頭像 UserInfoForComments fromUser = userClient.getAvatarByUserId(dto.getFromId()); if (fromUser != null) { dto.setFromAvatar(fromUser.getAvatar()); } //從用戶服務取被評論者頭像 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); } /** * 將無序的數據整理成有層級關系的數據 * * @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); //最外層的數據只添加 pid 為空的評論,其他評論在父評論的 children 下 if (dto1.getPid() == null || StringUtils.isEmpty(dto1.getPid())) { list.add(dto1); } } return list; } }
從數據庫取出來的評論是無序的,為了方便前端展示,需要對評論按層級排序,子評論在父評論的 children 字段中。
返回的數據:
{ "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 緩存數據
其實緩存已經在上面的代碼中做過了,兩個方法上的
@Cacheable(cacheNames = "comments", key = "#ownerId") @CacheEvict(cacheNames = "comments", key = "#dto.ownerId")
兩個注解就搞定了。第一次請求接口會走方法體
關于 Redis 的使用方法,我專門寫了篇文章介紹,就不在這里多說了,需要的可以看看這篇文章:
Redis詳解 - SpringBoot整合Redis,RedisTemplate和注解兩種方式的使用
以上就是對評論模塊的優化,歡迎大佬們提優化建議~
代碼出自開源項目 coderiver,致力于打造全平臺型全棧精品開源項目。
coderiver 中文名 河碼,是一個為程序員和設計師提供項目協作的平臺。無論你是前端、后端、移動端開發人員,或是設計師、產品經理,都可以在平臺上發布項目,與志同道合的小伙伴一起協作完成項目。
coderiver河碼 類似程序員客棧,但主要目的是方便各細分領域人才之間技術交流,共同成長,多人協作完成項目。暫不涉及金錢交易。
計劃做成包含 pc端(Vue、React)、移動H5(Vue、React)、ReactNative混合開發、Android原生、微信小程序、java后端的全平臺型全棧項目,歡迎關注。
項目地址:https://github.com/cachecats/...
您的鼓勵是我前行最大的動力,歡迎點贊,歡迎送小星星? ~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72272.html
摘要:無論你是前端后端移動端開發人員,或是設計師產品經理,都可以在平臺上發布項目,與志同道合的小伙伴一起協作完成項目。 全平臺全棧開源項目 coderiver 今天終于開始前后端聯調了~ 首先感謝大家的支持,coderiver 在 GitHub 上開源兩周,獲得了 54 個 Star,9 個 Fork,5 個 Watch。 這些鼓勵和認可也更加堅定了我繼續寫下去的決心~ 再次感謝各位大佬! ...
摘要:是一個相對比較新的微服務框架,年才推出的版本雖然時間最短但是相比等框架提供的全套的分布式系統解決方案。提供線程池不同的服務走不同的線程池,實現了不同服務調用的隔離,避免了服務器雪崩的問題。通過互相注冊的方式來進行消息同步和保證高可用。 Spring Cloud 是一個相對比較新的微服務框架,...
摘要:集群系統中的單個計算機通常稱為節點,通常通過局域網連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負責了。于是,小周和就變成了協作開發。都是為了項目正常運行以及迭代。 一、前言 只有光頭才能變強 認識我的朋友可能都知道我這陣子去實習啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
摘要:集群系統中的單個計算機通常稱為節點,通常通過局域網連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負責了。于是,小周和就變成了協作開發。都是為了項目正常運行以及迭代。 一、前言 只有光頭才能變強 認識我的朋友可能都知道我這陣子去實習啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
閱讀 1785·2023-04-25 21:50
閱讀 2432·2019-08-30 15:53
閱讀 775·2019-08-30 13:19
閱讀 2755·2019-08-28 17:58
閱讀 2477·2019-08-23 16:21
閱讀 2710·2019-08-23 14:08
閱讀 1386·2019-08-23 11:32
閱讀 1451·2019-08-22 16:09