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