目錄
一、達(dá)人探店
1.1、發(fā)布探店筆記
1.2、查看探店筆記
1.3、點(diǎn)贊功能
1.4、點(diǎn)贊排行榜
二、好友關(guān)注
2.1、關(guān)注和取消關(guān)注
2.2、共同關(guān)注
2.3、Feed流實(shí)現(xiàn)方案
2.4、推送到粉絲收件箱
2.4、實(shí)現(xiàn)分頁查詢收郵箱
一、達(dá)人探店
1.1、發(fā)布探店筆記
發(fā)布探店筆記
探店筆記類似點(diǎn)評(píng)網(wǎng)站的評(píng)價(jià),往往是圖文結(jié)合。對(duì)應(yīng)的表有兩個(gè):tb_blog:探店筆記表,包含筆記中的標(biāo)題、文字、圖片等,tb_blog_comments:其他用戶對(duì)探店筆記的評(píng)價(jià)。
具體發(fā)布流程
上傳文件接口
@Slf4j
@RestController
@RequestMapping("upload")
public class UploadController {
@PostMapping("blog")
public Result uploadImage(@RequestParam("file") MultipartFile image) {
try {
// 獲取原始文件名稱
String originalFilename = image.getOriginalFilename();
// 生成新文件名
String fileName = createNewFileName(originalFilename);
// 保存文件
image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));
// 返回結(jié)果
log.debug("文件上傳成功,{}", fileName);
return Result.ok(fileName);
} catch (IOException e) {
throw new RuntimeException("文件上傳失敗", e);
}
}
}
注意:在操作時(shí),需要修改SystemConstants.IMAGE_UPLOAD_DIR 自己圖片所在的地址,在實(shí)際開發(fā)中圖片一般會(huì)放在nginx上或者是云存儲(chǔ)上。
BlogController
@RestController
@RequestMapping("/blog")
public class BlogController {
@Resource
private IBlogService blogService;
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
//獲取登錄用戶
UserDTO user = UserHolder.getUser();
blog.setUpdateTime(user.getId());
//保存探店博文
blogService.saveBlog(blog);
//返回id
return Result.ok(blog.getId());
}
}
1.2、查看探店筆記
實(shí)現(xiàn)查看發(fā)布探店筆記的接口
實(shí)現(xiàn)代碼:
BlogServiceImpl
@Override
public Result queryBlogById(Long id) {
// 1.查詢blog
Blog blog = getById(id);
if (blog == null) {
return Result.fail("筆記不存在!");
}
// 2.查詢blog有關(guān)的用戶
queryBlogUser(blog);
return Result.ok(blog);
}
1.3、點(diǎn)贊功能
初始代碼
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id) {
//修改點(diǎn)贊數(shù)量
blogService.update().setSql("liked = liked + 1").eq("id",id).update();
return Result.ok();
}
問題分析:這種方式會(huì)導(dǎo)致一個(gè)用戶無限點(diǎn)贊,明顯是不合理的。
造成這個(gè)問題的原因是,我們現(xiàn)在的邏輯,發(fā)起請(qǐng)求只是給數(shù)據(jù)庫+1,所以才會(huì)出現(xiàn)這個(gè)問題。
完善點(diǎn)贊功能
需求:
-
同一個(gè)用戶只能點(diǎn)贊一次,再次點(diǎn)擊則取消點(diǎn)贊
-
如果當(dāng)前用戶已經(jīng)點(diǎn)贊,則點(diǎn)贊按鈕高亮顯示(前端已實(shí)現(xiàn),判斷字段Blog類的isLike屬性)
實(shí)現(xiàn)步驟:
-
給Blog類中添加一個(gè)isLike字段,標(biāo)示是否被當(dāng)前用戶點(diǎn)贊
-
修改點(diǎn)贊功能,利用Redis的set集合判斷是否點(diǎn)贊過,未點(diǎn)贊過則點(diǎn)贊數(shù)+1,已點(diǎn)贊過則點(diǎn)贊數(shù)-1
-
修改根據(jù)id查詢Blog的業(yè)務(wù),判斷當(dāng)前登錄用戶是否點(diǎn)贊過,賦值給isLike字段
-
修改分頁查詢Blog業(yè)務(wù),判斷當(dāng)前登錄用戶是否點(diǎn)贊過,賦值給isLike字段
為什么采用set集合:
因?yàn)槲覀兊臄?shù)據(jù)是不能重復(fù)的,當(dāng)用戶操作過之后,無論他怎么操作,都是
具體步驟:
1、在Blog 添加一個(gè)字段
@TableField(exist = false)
private Boolean isLike;
2、修改代碼
@Override
public Result likeBlog(Long id){
// 1.獲取登錄用戶
Long userId = UserHolder.getUser().getId();
// 2.判斷當(dāng)前登錄用戶是否已經(jīng)點(diǎn)贊
String key = BLOG_LIKED_KEY + id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
if(BooleanUtil.isFalse(isMember)){
//3.如果未點(diǎn)贊,可以點(diǎn)贊
//3.1 數(shù)據(jù)庫點(diǎn)贊數(shù)+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
//3.2 保存用戶到Redis的set集合
if(isSuccess){
stringRedisTemplate.opsForSet().add(key,userId.toString());
}
}else{
//4.如果已點(diǎn)贊,取消點(diǎn)贊
//4.1 數(shù)據(jù)庫點(diǎn)贊數(shù)-1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
//4.2 把用戶從Redis的set集合移除
if(isSuccess){
stringRedisTemplate.opsForSet().remove(key,userId.toString());
}
}
1.4、點(diǎn)贊排行榜
在探店筆記的詳情頁面,應(yīng)該把給該筆記點(diǎn)贊的人顯示出來,比如最早點(diǎn)贊的TOP5,形成點(diǎn)贊排行榜:
之前的點(diǎn)贊是放到set集合,但是set集合是不能排序的,所以這個(gè)時(shí)候,咱們可以采用一個(gè)可以排序的set集合,就是咱們的sortedSet
我們接下來來對(duì)比一下這些集合的區(qū)別是什么?
所有點(diǎn)贊的人,需要是唯一的,所以我們應(yīng)當(dāng)使用set或者是sortedSet
其次我們需要排序,就可以直接鎖定使用sortedSet啦
修改代碼
BlogServiceImpl
點(diǎn)贊邏輯代碼
@Override
public Result likeBlog(Long id) {
// 1.獲取登錄用戶
Long userId = UserHolder.getUser().getId();
// 2.判斷當(dāng)前登錄用戶是否已經(jīng)點(diǎn)贊
String key = BLOG_LIKED_KEY + id;
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
if (score == null) {
// 3.如果未點(diǎn)贊,可以點(diǎn)贊
// 3.1.數(shù)據(jù)庫點(diǎn)贊數(shù) + 1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
// 3.2.保存用戶到Redis的set集合 zadd key value score
if (isSuccess) {
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
} else {
// 4.如果已點(diǎn)贊,取消點(diǎn)贊
// 4.1.數(shù)據(jù)庫點(diǎn)贊數(shù) -1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
// 4.2.把用戶從Redis的set集合移除
if (isSuccess) {
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
private void isBlogLiked(Blog blog) {
// 1.獲取登錄用戶
UserDTO user = UserHolder.getUser();
if (user == null) {
// 用戶未登錄,無需查詢是否點(diǎn)贊
return;
}
Long userId = user.getId();
// 2.判斷當(dāng)前登錄用戶是否已經(jīng)點(diǎn)贊
String key = "blog:liked:" + blog.getId();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
blog.setIsLike(score != null);
}
點(diǎn)贊列表查詢列表
BlogController
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id) {
return blogService.queryBlogLikes(id);
}
BlogService
@Override
public Result queryBlogLikes(Long id) {
String key = BLOG_LIKED_KEY + id;
// 1.查詢top5的點(diǎn)贊用戶 zrange key 0 4
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 2.解析出其中的用戶id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
String idStr = StrUtil.join(",", ids);
// 3.根據(jù)用戶id查詢用戶 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)
List<UserDTO> userDTOS = userService.query()
.in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
// 4.返回
return Result.ok(userDTOS);
}
二、好友關(guān)注
2.1、關(guān)注和取消關(guān)注
針對(duì)用戶的操作:可以對(duì)用戶進(jìn)行關(guān)注和取消關(guān)注功能。
實(shí)現(xiàn)思路:
需求:基于該表數(shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)兩個(gè)接口:
-
關(guān)注和取關(guān)接口
-
判斷是否關(guān)注的接口
關(guān)注是User之間的關(guān)系,是博主與粉絲的關(guān)系,數(shù)據(jù)庫中有一張tb_follow表來標(biāo)示:
注意: 這里需要把主鍵修改為自增長(zhǎng),簡(jiǎn)化開發(fā)。
FollowController
//關(guān)注
@PutMapping("/{id}/{isFollow}")
public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow) {
return followService.follow(followUserId, isFollow);
}
//取消關(guān)注
@GetMapping("/or/not/{id}")
public Result isFollow(@PathVariable("id") Long followUserId) {
return followService.isFollow(followUserId);
}
FollowService
//查詢是否已關(guān)注
@Override
public Result isFollow(Long followUserId) {
// 1.獲取登錄用戶
Long userId = UserHolder.getUser().getId();
// 2.查詢是否關(guān)注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?
Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
// 3.判斷
return Result.ok(count > 0);
}
//關(guān)注service
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// 1.獲取登錄用戶
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
// 1.判斷到底是關(guān)注還是取關(guān)
if (isFollow) {
// 2.關(guān)注,新增數(shù)據(jù)
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
boolean isSuccess = save(follow);
} else {
// 3.取關(guān),刪除 delete from tb_follow where user_id = ? and follow_user_id = ?
remove(new QueryWrapper<Follow>()
.eq("user_id", userId).eq("follow_user_id", followUserId));
}
return Result.ok();
}
2.2、共同關(guān)注
想要去看共同關(guān)注的好友,需要首先進(jìn)入到這個(gè)頁面,這個(gè)頁面會(huì)發(fā)起兩個(gè)請(qǐng)求
1、去查詢用戶的詳情
2、去查詢用戶的筆記
以上兩個(gè)功能和共同關(guān)注沒有什么關(guān)系,大家可以自行將筆記中的代碼拷貝到idea中就可以實(shí)現(xiàn)這兩個(gè)功能了,我們的重點(diǎn)在于共同關(guān)注功能。
// UserController 根據(jù)id查詢用戶
@GetMapping("/{id}")
public Result queryUserById(@PathVariable("id") Long userId){
// 查詢?cè)斍? User user = userService.getById(userId);
if (user == null) {
return Result.ok();
}
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 返回
return Result.ok(userDTO);
}
// BlogController 根據(jù)id查詢博主的探店筆記
@GetMapping("/of/user")
public Result queryBlogByUserId(
@RequestParam(value = "current", defaultValue = "1") Integer current,
@RequestParam("id") Long id) {
// 根據(jù)用戶查詢
Page<Blog> page = blogService.query()
.eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 獲取當(dāng)前頁數(shù)據(jù)
List<Blog> records = page.getRecords();
return Result.ok(records);
}
接下來我們來看看共同關(guān)注如何實(shí)現(xiàn):
需求:利用Redis中恰當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)共同關(guān)注功能。在博主個(gè)人頁面展示出當(dāng)前用戶與博主的共同關(guān)注呢。
當(dāng)然是使用我們之前學(xué)習(xí)過的set集合咯,在set集合中,有交集并集補(bǔ)集的api,我們可以把兩人的關(guān)注的人分別放入到一個(gè)set集合中,然后再通過api去查看這兩個(gè)set集合中的交集數(shù)據(jù)。
我們先來改造當(dāng)前的關(guān)注列表
改造原因是因?yàn)槲覀冃枰谟脩絷P(guān)注了某位用戶后,需要將數(shù)據(jù)放入到set集合中,方便后續(xù)進(jìn)行共同關(guān)注,同時(shí)當(dāng)取消關(guān)注時(shí),也需要從set集合中進(jìn)行刪除
FollowServiceImpl
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// 1.獲取登錄用戶
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
// 1.判斷到底是關(guān)注還是取關(guān)
if (isFollow) {
// 2.關(guān)注,新增數(shù)據(jù)
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
boolean isSuccess = save(follow);
if (isSuccess) {
// 把關(guān)注用戶的id,放入redis的set集合 sadd userId followerUserId
stringRedisTemplate.opsForSet().add(key, followUserId.toString());
}
} else {
// 3.取關(guān),刪除 delete from tb_follow where user_id = ? and follow_user_id = ?
boolean isSuccess = remove(new QueryWrapper<Follow>()
.eq("user_id", userId).eq("follow_user_id", followUserId));
if (isSuccess) {
// 把關(guān)注用戶的id從Redis集合中移除
stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
}
}
return Result.ok();
}
具體的關(guān)注代碼:
FollowServiceImpl
@Override
public Result followCommons(Long id) {
// 1.獲取當(dāng)前用戶
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
// 2.求交集
String key2 = "follows:" + id;
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
if (intersect == null || intersect.isEmpty()) {
// 無交集
return Result.ok(Collections.emptyList());
}
// 3.解析id集合
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
// 4.查詢用戶
List<UserDTO> users = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(users);
}
2.3、Feed流實(shí)現(xiàn)方案
當(dāng)我們關(guān)注了用戶后,這個(gè)用戶發(fā)了動(dòng)態(tài),那么我們應(yīng)該把這些數(shù)據(jù)推送給用戶,這個(gè)需求,其實(shí)我們又把他叫做Feed流,關(guān)注推送也叫做Feed流,直譯為投喂。為用戶持續(xù)的提供“沉浸式”的體驗(yàn),通過無限下拉刷新獲取新的信息。
對(duì)于傳統(tǒng)的模式的內(nèi)容解鎖:我們是需要用戶去通過搜索引擎或者是其他的方式去解鎖想要看的內(nèi)容。
對(duì)于新型的Feed流的效果:不需要我們用戶再去推送信息,而是系統(tǒng)分析用戶到底想要什么,然后直接把內(nèi)容推送給用戶,從而使用戶能夠更加的節(jié)約時(shí)間,不用主動(dòng)去尋找。 ?
Feed流的實(shí)現(xiàn)有兩種模式:
Feed流產(chǎn)品有兩種常見模式:Timeline:不做內(nèi)容篩選,簡(jiǎn)單的按照內(nèi)容發(fā)布時(shí)間排序,常用于好友或關(guān)注。例如朋友圈
-
優(yōu)點(diǎn):信息全面,不會(huì)有缺失。并且實(shí)現(xiàn)也相對(duì)簡(jiǎn)單
-
缺點(diǎn):信息噪音較多,用戶不一定感興趣,內(nèi)容獲取效率低
智能排序:利用智能算法屏蔽掉違規(guī)的、用戶不感興趣的內(nèi)容。推送用戶感興趣信息來吸引用戶
-
優(yōu)點(diǎn):投喂用戶感興趣信息,用戶粘度很高,容易沉迷
-
缺點(diǎn):如果算法不精準(zhǔn),可能起到反作用本例中的個(gè)人頁面,是基于關(guān)注的好友來做Feed流,因此采用Timeline的模式。該模式的實(shí)現(xiàn)方案有三種:
我們本次針對(duì)好友的操作,采用的就是Timeline的方式,只需要拿到我們關(guān)注用戶的信息,然后按照時(shí)間排序即可
因此采用Timeline的模式。該模式的實(shí)現(xiàn)方案有三種:
-
拉模式
-
推模式
-
推拉結(jié)合
拉模式:也叫做讀擴(kuò)散。
該模式的核心含義就是:當(dāng)張三和李四和王五發(fā)了消息后,都會(huì)保存在自己的郵箱中,假設(shè)趙六要讀取信息,那么他會(huì)從讀取他自己的收件箱,此時(shí)系統(tǒng)會(huì)從他關(guān)注的人群中,把他關(guān)注人的信息全部都進(jìn)行拉取,然后在進(jìn)行排序。
優(yōu)點(diǎn):比較節(jié)約空間,因?yàn)橼w六在讀信息時(shí),并沒有重復(fù)讀取,而且讀取完之后可以把他的收件箱進(jìn)行清楚。
缺點(diǎn):比較延遲,當(dāng)用戶讀取數(shù)據(jù)時(shí)才去關(guān)注的人里邊去讀取數(shù)據(jù),假設(shè)用戶關(guān)注了大量的用戶,那么此時(shí)就會(huì)拉取海量的內(nèi)容,對(duì)服務(wù)器壓力巨大。
?
推模式:也叫做寫擴(kuò)散。
推模式是沒有寫郵箱的,當(dāng)張三寫了一個(gè)內(nèi)容,此時(shí)會(huì)主動(dòng)的把張三寫的內(nèi)容發(fā)送到他的粉絲收件箱中去,假設(shè)此時(shí)李四再來讀取,就不用再去臨時(shí)拉取了。
優(yōu)點(diǎn):時(shí)效快,不用臨時(shí)拉取。
缺點(diǎn):內(nèi)存壓力大,假設(shè)一個(gè)大V寫信息,很多人關(guān)注他, 就會(huì)寫很多分?jǐn)?shù)據(jù)到粉絲那邊去。
推拉結(jié)合模式:也叫做讀寫混合,兼具推和拉兩種模式的優(yōu)點(diǎn)。
推拉模式是一個(gè)折中的方案,站在發(fā)件人這一段,如果是個(gè)普通的人,那么我們采用寫擴(kuò)散的方式,直接把數(shù)據(jù)寫入到他的粉絲中去,因?yàn)槠胀ǖ娜怂姆劢z關(guān)注量比較小,所以這樣做沒有壓力,如果是大V,那么他是直接將數(shù)據(jù)先寫入到一份到發(fā)件箱里邊去,然后再直接寫一份到活躍粉絲收件箱里邊去,現(xiàn)在站在收件人這端來看,如果是活躍粉絲,那么大V和普通的人發(fā)的都會(huì)直接寫入到自己收件箱里邊來,而如果是普通的粉絲,由于他們上線不是很頻繁,所以等他們上線時(shí),再從發(fā)件箱里邊去拉信息。
2.4、推送到粉絲收件箱
需求:
-
修改新增探店筆記的業(yè)務(wù),在保存blog到數(shù)據(jù)庫的同時(shí),推送到粉絲的收件箱。
-
收件箱滿足可以根據(jù)時(shí)間戳排序,必須用Redis的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。
-
查詢收件箱數(shù)據(jù)時(shí),可以實(shí)現(xiàn)分頁查詢。
Feed流中的數(shù)據(jù)會(huì)不斷更新,所以數(shù)據(jù)的角標(biāo)也在變化,因此不能采用傳統(tǒng)的分頁模式。
傳統(tǒng)了分頁在feed流是不適用的,因?yàn)槲覀兊臄?shù)據(jù)會(huì)隨時(shí)發(fā)生變化。
假設(shè)在t1 時(shí)刻,我們?nèi)プx取第一頁,此時(shí)page = 1 ,size = 5 ,那么我們拿到的就是10~6 這幾條記錄,假設(shè)現(xiàn)在t2時(shí)候又發(fā)布了一條記錄,此時(shí)t3 時(shí)刻,我們來讀取第二頁,讀取第二頁傳入的參數(shù)是page=2 ,size=5 ,那么此時(shí)讀取到的第二頁實(shí)際上是從6 開始,然后是6~2 ,那么我們就讀取到了重復(fù)的數(shù)據(jù),所以feed流的分頁,不能采用原始方案來做。
Feed流的滾動(dòng)分頁。
我們需要記錄每次操作的最后一條,然后從這個(gè)位置開始去讀取數(shù)據(jù)。
舉個(gè)例子:我們從t1時(shí)刻開始,拿第一頁數(shù)據(jù),拿到了10~6,然后記錄下當(dāng)前最后一次拿取的記錄,就是6,t2時(shí)刻發(fā)布了新的記錄,此時(shí)這個(gè)11放到最頂上,但是不會(huì)影響我們之前記錄的6,此時(shí)t3時(shí)刻來拿第二頁,第二頁這個(gè)時(shí)候拿數(shù)據(jù),還是從6后一點(diǎn)的5去拿,就拿到了5-1的記錄。我們這個(gè)地方可以采用sortedSet來做,可以進(jìn)行范圍查詢,并且還可以記錄當(dāng)前獲取數(shù)據(jù)時(shí)間戳最小值,就可以實(shí)現(xiàn)滾動(dòng)分頁了。
核心的意思:就是我們?cè)诒4嫱晏降旯P記后,獲得到當(dāng)前筆記的粉絲,然后把數(shù)據(jù)推送到粉絲的redis中去。 ?
@Override
public Result saveBlog(Blog blog) {
// 1.獲取登錄用戶
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 2.保存探店筆記
boolean isSuccess = save(blog);
if(!isSuccess){
return Result.fail("新增筆記失敗!");
}
// 3.查詢筆記作者的所有粉絲 select * from tb_follow where follow_user_id = ?
List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
// 4.推送筆記id給所有粉絲
for (Follow follow : follows) {
// 4.1.獲取粉絲id
Long userId = follow.getUserId();
// 4.2.推送
String key = FEED_KEY + userId;
stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
}
// 5.返回id
return Result.ok(blog.getId());
}
2.5、實(shí)現(xiàn)分頁查詢收郵箱
需求:在個(gè)人主頁的“關(guān)注”卡片中,查詢并展示推送的Blog信息:
具體操作如下:
1、每次查詢完成后,我們要分析出查詢出數(shù)據(jù)的最小時(shí)間戳,這個(gè)值會(huì)作為下一次查詢的條件
2、我們需要找到與上一次查詢相同的查詢個(gè)數(shù)作為偏移量,下次查詢時(shí),跳過這些查詢過的數(shù)據(jù),拿到我們需要的數(shù)據(jù)
綜上:我們的請(qǐng)求參數(shù)中就需要攜帶 lastId:上一次查詢的最小時(shí)間戳 和偏移量這兩個(gè)參數(shù)。
這兩個(gè)參數(shù)第一次會(huì)由前端來指定,以后的查詢就根據(jù)后臺(tái)結(jié)果作為條件,再次傳遞到后臺(tái)。
一、定義出來具體的返回值實(shí)體類
@Data
public class ScrollResult {
private List<?> list;
private Long minTime;
private Integer offset;
}
BlogController
注意:RequestParam 表示接受url地址欄傳參的注解,當(dāng)方法上參數(shù)的名稱和url地址欄不相同時(shí),可以通過RequestParam 來進(jìn)行指定。文章來源:http://www.zghlxwxcb.cn/news/detail-468873.html
@GetMapping("/of/follow")
public Result queryBlogOfFollow(
@RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){
return blogService.queryBlogOfFollow(max, offset);
}
BlogServiceImpl文章來源地址http://www.zghlxwxcb.cn/news/detail-468873.html
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// 1.獲取當(dāng)前用戶
Long userId = UserHolder.getUser().getId();
// 2.查詢收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
String key = FEED_KEY + userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 2);
// 3.非空判斷
if (typedTuples == null || typedTuples.isEmpty()) {
return Result.ok();
}
// 4.解析數(shù)據(jù):blogId、minTime(時(shí)間戳)、offset
List<Long> ids = new ArrayList<>(typedTuples.size());
long minTime = 0; // 2
int os = 1; // 2
for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
// 4.1.獲取id
ids.add(Long.valueOf(tuple.getValue()));
// 4.2.獲取分?jǐn)?shù)(時(shí)間戳)
long time = tuple.getScore().longValue();
if(time == minTime){
os++;
}else{
minTime = time;
os = 1;
}
}
os = minTime == max ? os : os + offset;
// 5.根據(jù)id查詢blog
String idStr = StrUtil.join(",", ids);
List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
for (Blog blog : blogs) {
// 5.1.查詢blog有關(guān)的用戶
queryBlogUser(blog);
// 5.2.查詢blog是否被點(diǎn)贊
isBlogLiked(blog);
}
// 6.封裝并返回
ScrollResult r = new ScrollResult();
r.setList(blogs);
r.setOffset(os);
r.setMinTime(minTime);
return Result.ok(r);
}
到了這里,關(guān)于黑馬Redis視頻教程實(shí)戰(zhàn)篇(五)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!