達人探店
發布探店筆記
探店筆記類似于點評網站的評價,往往是圖文結合,對應的表有兩個:
tb_blog:探店筆記表,包含筆記中的標題、文字、圖片等
tb_blog_comments:其他用戶對探店筆記的評價
tb_blog表結構如下:
可以從結構中看出,跟發布探店筆記相關的字段有shop_id,title,images,content,liked,comments
業務流程:
點擊首頁最下方菜單欄的+按鈕,即可發布探店圖文:
需要注意的是發布照片與發布筆記是兩個分離的功能,因為上傳照片的功能不僅僅是發筆記有這個需求,在其他業務中可能也會需要發布照片,因此上傳照片功能是一個獨立功能,點擊上傳照片時會先發出一個請求實現上傳,上傳成功后返回這張圖片的地址,也就是上傳之后的可訪問的地址,這個地址就會做為表單的參數,在發布筆記時一起提交到后臺,即在發布筆記時上傳的其實不是照片的本身,而是上傳成功后的圖片地址,因此發布照片和發布筆記是不同的功能,會發起兩個不同的請求。
而黑馬點評這邊已經將接口寫好了,http: //127.0.0.1:8081/upload/blog,這個接口是為了上傳圖片的,因此需要修改存放的地址,是將其存放在前端服務器中的目錄中,另一個接口http: //127.0.0.1:8081/blog,上傳博客。
測試接口:
可以查看數據庫表:
而在發布完探店筆記后,發現沒有實現查看探店筆記的接口。
因此:
案例展示:實現查看探店筆記的接口
需求:點擊首頁的探店筆記,會進入詳情頁面,實現該頁面的查詢接口:
controller層:
@GetMapping("/{id}")
public Result queryBlog(@PathVariable("id") Long id){return blogService.queryBlogById(id);
}
service層
@Override
public Result queryBlogById(Long id) {// 1.查詢blogBlog blog = getById(id);if (blog == null) {return Result.fail("筆記不存在!");}// 2.查詢blog有關的用戶queryBlogUser(blog);return Result.ok(blog);
}private void queryBlogUser(Blog blog) {Long userId = blog.getUserId();User user = userService.getById(userId);blog.setName(user.getNickName());blog.setIcon(user.getIcon());}
測試:
點贊
在首頁的探店筆記排行榜和探店圖文詳情頁面都有點贊功能:
在Java代碼中直接利用MyBatisPlus來修改數據庫中的liked字段,每一次點贊都是進行一次接口請求,并且沒有點贊限制,沒有進行用戶判斷。而且是直接修改數據庫,影響性能。
案例:完善點贊功能
需求:
同一個用戶只能點贊一次,再次點擊則為取消點贊
如果當前用戶已經點贊,則點贊按鈕高亮(前端已實現,判斷字段blog類的isLike屬性)
實現思路:可以在新建一個表,記錄筆記的id,以及點贊用戶的id,這樣每次點贊只需要看表中存在不存在這個用戶的值即可,不需要去使用MySQL數據庫,可以使用輕量級的數據庫,比如redis,可以使用集合類的數據結構,key為筆記的id,value則為點贊的用戶id,并且一個用戶只能點贊一次,所以value不能重復,所以可以使用set數據結構。
實現步驟:
給blog類中添加一個isLike字段,標識是否被當前用戶點贊
修改點贊功能,利用redis的set集合判斷是否點贊過,未點贊過則點贊數+1,已點贊過則點贊數-1,并且點贊過的用戶需要添加在set集合中。
修改根據id查詢blog的業務,判斷當前登錄用戶是否點贊過,賦值給isLike字段
修改分頁查詢blog業務,判斷當前登錄用戶是否點贊過,賦值給isLike字段
代碼實現:
?/*** 用戶圖標*/@TableField(exist = false)private String icon;/*** 用戶姓名*/@TableField(exist = false)private String name;/*** 是否點贊過了*///該注解表示該成員變量不是數據庫表中的字段@TableField(exist = false)private Boolean isLike;
service層業務代碼:
?public Result likeBlog(Long id) {//1.獲取登錄用戶Long userId = UserHolder.getUser().getId();//2.判斷當前登錄用戶是否已經點贊String key = BLOG_LIKED_KEY + id;Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());if(Boolean.FALSE.equals(isMember)){//3.如果未點贊,可以點贊//3.1點贊數+1boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() + 1).eq(Blog::getId, id).update();//3.2,將用戶id存儲到redis的set集合中if (success) {stringRedisTemplate.opsForSet().add(key, userId.toString());}}else {//4.如果已點贊,取消點贊//4.1.數據庫點贊數-1,boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() - 1).eq(Blog::getId, id).update();//4.2將用戶id從Redis的set集合中刪除if (success) {stringRedisTemplate.opsForSet().remove(key, userId.toString());}}return Result.ok();}
其次,我們還需要修改查詢blog的業務代碼,在查詢用戶之后,還需要查詢該博客是否被點贊,再去修改isLiked的值。
由于查詢有兩種查詢,一種分頁查詢,一種普通查詢,因此我們將是否被點贊這個業務封裝成函數,進行簡化代碼。
代碼實現:
private void isBlogLiked(Blog blog) {Long userId = UserHolder.getUser().getId();String key = BLOG_LIKED_KEY + blog.getId();Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());//防止包裝類為空blog.setIsLike(Boolean.TRUE.equals(isMember));}
進行測試:
進行點贊:
檢查redis數據庫:
取消點贊:
再次查看Redis數據庫:
發現已經不存在。
至此業務實現成功。
點贊排行榜
業務詳情:
在探店筆記的詳情頁面,應該把給該筆記點贊的人顯示出來,比如最早點贊的top5,形成點贊排行榜:
我們可以借鑒微信朋友圈點贊的機制。誰先點贊,誰在前面
接口信息:
案例實現:實現查詢點贊排行榜的接口
需求:按照點贊時間先后排序,返回top5的用戶
思路分析:
我們需要按照點贊時間先后排序,但是點贊的用戶信息存儲在redis中的set集合里,set集合是無序集合,因此我們需要去更換集合,同時還要滿足唯一性與有序性,將Redis中的集合數據結構進行對比。
List | Set | SortedSet | |
---|---|---|---|
排序方式 | 按添加順序排序 | 無序 | 可排序,根據score值 |
唯一性 | 不唯一 | 唯一 | 唯一 |
查找方式 | 按照索引查找或者首尾查找 | 按照元素查找 | 根據元素查找 |
由此看來,SortedSet更符合業務需求。
但是SortedSet和set有很多不同之處,就別去set中有sismember命令去查詢某個元素是否在集合內,但是SortedSet就沒有這樣的命令,但是我們可以使用zscore命令來查詢score值,如果元素不存在則score值就不存在,如果存在則有score值。這個命令也可以判斷元素是否存在。如果查詢排行榜可以使用zrange命令來查詢。zrange命令就是查找范圍內的元素。并且會天然的排序,可以按照時間戳來排序時,就會按照時間戳從小到大排序,最早插入的在最前面,這樣就可以完成查詢排行榜的業務。
思路實現:
改造之前的點贊業務,將set集合替換為SortedSet集合:
?private void isBlogLiked(Blog blog) {Long userId = UserHolder.getUser().getId();String key = BLOG_LIKED_KEY + blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());//防止包裝類為空blog.setIsLike(score != null);}?@Overridepublic Result likeBlog(Long id) {//1.獲取登錄用戶Long userId = UserHolder.getUser().getId();//2.判斷當前登錄用戶是否已經點贊String key = BLOG_LIKED_KEY + id;Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());if(score == null){//3.如果未點贊,可以點贊//3.1點贊數+1boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() + 1).eq(Blog::getId, id).update();//3.2,將用戶id存儲到redis的set集合中if (success) {stringRedisTemplate.opsForZSet().add(key, userId.toString(),System.currentTimeMillis());}}else {//4.如果已點贊,取消點贊//4.1.數據庫點贊數-1,boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() - 1).eq(Blog::getId, id).update();//4.2將用戶id從Redis的set集合中刪除if (success) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();}
實現排行榜接口:
?@Overridepublic Result queryBlogLikes(Long id) {//1.獲取當前blog的點贊top5String key = BLOG_LIKED_KEY + id;Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);//2.解析出其中的用戶id,map映射,將long類型的字符串轉為Longif (top5 == null || top5.isEmpty()) {return Result.ok(Collections.emptyList());}List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());//3.根據用戶ID查詢用戶,這里直接返回的是user對象,但是在前端頁面應該顯示的是UserDTO,所以這里需要將user對象轉為UserDTO對象List<UserDTO> userDTOS = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());//4.返回return Result.ok(userDTOS);}
進行測試:
先嘗試一個賬號點贊:
測試成功,在進行多個賬戶點贊:
發現bug:新開網頁時,發現下面的博客不登錄看不到,根據報錯提示發現是空指針異常,即userid為空,解決方案,在從UserHolder中取user時先進行判斷,如果為空,則直接return;
代碼演示:
private void isBlogLiked(Blog blog) {UserDTO user = UserHolder.getUser();if (user == null){return;}Long userId = user.getId();String key = BLOG_LIKED_KEY + blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());//防止包裝類為空blog.setIsLike(score != null);}
解決;
繼續測試:
發現順序不對,去IDEA中檢查:
發現順序是對的,在進入數據庫中查詢:
發現是使用關鍵字in的問題,
因此要使用order by ,但不能直接指定id,因為默認order by id 的話是按順序從小到大的,因此要指定順序:
因此需要修改查詢語句,又因為MyBatisPlus沒有封裝該方法,只能去自定義SQL語句。
修改代碼如下:
?//3.根據用戶ID查詢用戶,這里直接返回的是user對象,但是在前端頁面應該顯示的是UserDTO,所以這里需要將user對象轉為UserDTO對象//where id in (ids) order by field(id,ids)List<UserDTO> userDTOS = userService.lambdaQuery().in(User::getId, ids).last("order by field(id,"+ idStr+")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
再次進行刷新測試:
至此,點贊排行榜業務實現成功。
希望對大家有所幫助!