Selaa lähdekoodia

0724 定时任务实现日更排行榜

Qing 10 kuukautta sitten
vanhempi
commit
67914af9df

+ 3 - 0
novel-demo/src/main/java/com/sf/NovelDemoApplication.java

@@ -2,8 +2,11 @@ package com.sf;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
+// @EnableScheduling 开启定时任务
 @SpringBootApplication
+@EnableScheduling
 public class NovelDemoApplication {
 
     public static void main(String[] args) {

+ 23 - 0
novel-demo/src/main/java/com/sf/cons/RedisKeyConst.java

@@ -0,0 +1,23 @@
+package com.sf.cons;
+
+// redis key的常量类
+public class RedisKeyConst {
+    // 点击量排行榜
+    public final static String VISIT_ZSET = "book:visitZset";
+    // 新书排行榜
+    public final static String NEW_ZSET = "book:newZset";
+    // 更新排行榜
+    public final static String UPDATE_ZSET = "book:updateZset";
+    // 同类推荐
+    public final static String REC_BOOKS_SET = "book:myRecBooksSet";
+
+    // 存储首页id
+    public final static String HOME_BOOK_LIST_0 = "book:homeBookList0";
+    public final static String HOME_BOOK_LIST_1 = "book:homeBookList1";
+    public final static String HOME_BOOK_LIST_2 = "book:homeBookList2";
+    public final static String HOME_BOOK_LIST_3 = "book:homeBookList3";
+    public final static String HOME_BOOK_LIST_4 = "book:homeBookList4";
+    // 存储书籍id 和书籍信息的hash
+    public final static String BOOK_HASH = "book:bookHash";
+    // 验证码和token String
+}

+ 1 - 1
novel-demo/src/main/java/com/sf/controller/BookInfoController.java

@@ -106,7 +106,7 @@ public class BookInfoController {
     public RestResp<Void> addVisitCount(@Parameter(description = "小说ID")
                                         @RequestBody BookVisitReqDto reqDto) {
         long bookId = Long.parseLong(reqDto.getBookId());
-        bookRankService.updateVisitRank(bookId);
+//        bookRankService.updateVisitRank(bookId);
         bookInfoService.addVisitCount(bookId);
         return RestResp.ok(null);
     }

+ 0 - 3
novel-demo/src/main/java/com/sf/controller/BookRankController.java

@@ -1,16 +1,13 @@
 package com.sf.controller;
 
 import com.sf.dto.RestResp;
-import com.sf.dto.resp.BookChapterAboutRespDto;
 import com.sf.dto.resp.BookRankRespDto;
 import com.sf.service.IBookRankService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;

+ 2 - 1
novel-demo/src/main/java/com/sf/service/impl/BookInfoServiceImpl.java

@@ -2,6 +2,7 @@ package com.sf.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.sf.cons.RedisKeyConst;
 import com.sf.dto.resp.BookCategoryRespDto;
 import com.sf.dto.req.BookSearchReqDto;
 import com.sf.dto.resp.BookInfoRespDto;
@@ -136,7 +137,7 @@ public class BookInfoServiceImpl extends ServiceImpl<BookInfoMapper, BookInfo>
         // 返回的书籍列表中 包含初始的书籍
         List<BookInfo> bookInfos = bookInfoMapper.selectList(queryWrapper);
 
-        String key = "book:myRecBooksSet" + bookInfo.getCategoryId();
+        String key = RedisKeyConst.REC_BOOKS_SET + bookInfo.getCategoryId();
         SetOperations setOperations = redisTemplate.opsForSet();
         for (BookInfo info : bookInfos) {
             setOperations.add(key, info.getId());

+ 28 - 13
novel-demo/src/main/java/com/sf/service/impl/BookRankByRedisServiceImpl.java

@@ -1,13 +1,13 @@
 package com.sf.service.impl;
 
 import com.google.gson.Gson;
+import com.sf.cons.RedisKeyConst;
 import com.sf.dto.resp.BookRankRespDto;
 import com.sf.entity.BookInfo;
 import com.sf.mapper.BookInfoMapper;
 import com.sf.service.IBookRankService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.ZSetOperations;
 import org.springframework.stereotype.Service;
@@ -27,18 +27,16 @@ public class BookRankByRedisServiceImpl implements IBookRankService {
     @Override
     public List<BookRankRespDto> listVisitRankBooks() {
         // 将书籍对象的json放入zset的value中 点击量放入scores
-        String key = "book:visitZset";
         // 当zset中已经有值时  只需要从zset中取数据
         ZSetOperations zSetOperations = redisTemplate.opsForZSet();
-        Long size = zSetOperations.size(key);
+        Long size = zSetOperations.size(RedisKeyConst.VISIT_ZSET);
         if (size == 0) {
             System.out.println("visitZset size:" + size);
             initData();
         }
-        Set set = zSetOperations.reverseRange(key, 0, 30);
-        return set.stream().map(obj -> {
+        Set<String> set = zSetOperations.reverseRange(RedisKeyConst.VISIT_ZSET, 0, 29);
+        return set.stream().map(str -> {
             // 将map中的每一个元素 转化为BookRankRespDto
-            String str = (String) obj;
             BookInfo bookInfo = gson.fromJson(str, BookInfo.class);
             BookRankRespDto bookRankRespDto = new BookRankRespDto();
             BeanUtils.copyProperties(bookInfo, bookRankRespDto);
@@ -48,36 +46,53 @@ public class BookRankByRedisServiceImpl implements IBookRankService {
 
     // 数据初始化
     private void initData() {
-        String key = "book:visitZset";
         List<BookInfo> bookInfos = bookInfoMapper.selectList(null);
         ZSetOperations zSetOperations = redisTemplate.opsForZSet();
         for (BookInfo bookInfo : bookInfos) {
             Long visitCount = bookInfo.getVisitCount();
             String bookInfoJson = gson.toJson(bookInfo);
-            zSetOperations.add(key, bookInfoJson, visitCount);
+            zSetOperations.add(RedisKeyConst.VISIT_ZSET, bookInfoJson, visitCount);
         }
     }
 
     @Override
     public List<BookRankRespDto> listNewestRankBooks() {
-        return List.of();
+        String key = RedisKeyConst.NEW_ZSET;
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        Set<String> set = zSetOperations.reverseRange(key, 0, 29);
+        return set.stream().map(str -> {
+            // 将map中的每一个元素 转化为BookRankRespDto
+            BookInfo bookInfo = gson.fromJson(str, BookInfo.class);
+            BookRankRespDto bookRankRespDto = new BookRankRespDto();
+            BeanUtils.copyProperties(bookInfo, bookRankRespDto);
+            return bookRankRespDto;
+        }).toList();
     }
 
     @Override
     public List<BookRankRespDto> listUpdateRankBooks() {
-        return List.of();
+        String key = RedisKeyConst.UPDATE_ZSET;
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        Set<String> set = zSetOperations.reverseRange(key, 0, 29);
+        return set.stream().map(str -> {
+            // 将map中的每一个元素 转化为BookRankRespDto
+            BookInfo bookInfo = gson.fromJson(str, BookInfo.class);
+            BookRankRespDto bookRankRespDto = new BookRankRespDto();
+            BeanUtils.copyProperties(bookInfo, bookRankRespDto);
+            return bookRankRespDto;
+        }).toList();
     }
 
+    // 这是实时榜单的处理逻辑
     @Override
     public void updateVisitRank(Long bookId) {
-        String key = "book:visitZset";
         BookInfo bookInfo = bookInfoMapper.selectById(bookId);
         String json = gson.toJson(bookInfo);
         // 此时的问题 是json中包含了访问量参数 而访问量会频繁变化
         ZSetOperations zSetOperations = redisTemplate.opsForZSet();
         // 每一次更新访问量 就将旧的数据移除  新的数据添加进去  保证数据的统一
-        zSetOperations.remove(key, json);
+        zSetOperations.remove(RedisKeyConst.VISIT_ZSET, json);
         bookInfo.setVisitCount(bookInfo.getVisitCount() + 1);
-        zSetOperations.add(key, gson.toJson(bookInfo), bookInfo.getVisitCount());
+        zSetOperations.add(RedisKeyConst.VISIT_ZSET, gson.toJson(bookInfo), bookInfo.getVisitCount());
     }
 }

+ 13 - 13
novel-demo/src/main/java/com/sf/service/impl/HomeBookServiceImpl.java

@@ -3,6 +3,7 @@ package com.sf.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.google.gson.Gson;
+import com.sf.cons.RedisKeyConst;
 import com.sf.dto.resp.HomeBookRespDto;
 import com.sf.dto.resp.HomeFriendLinkRespDto;
 import com.sf.entity.BookInfo;
@@ -130,18 +131,17 @@ public class HomeBookServiceImpl extends ServiceImpl<HomeBookMapper, HomeBook> i
             String json = gson.toJson(bookInfo);
             homeBookMap.put(bookId.toString(), json);
         }
-        String key0 = "book:homeBookList0";
-        String key1 = "book:homeBookList1";
-        String key2 = "book:homeBookList2";
-        String key3 = "book:homeBookList3";
-        String key4 = "book:homeBookList4";
+        String key0 = RedisKeyConst.HOME_BOOK_LIST_0;
+        String key1 = RedisKeyConst.HOME_BOOK_LIST_1;
+        String key2 = RedisKeyConst.HOME_BOOK_LIST_2;
+        String key3 = RedisKeyConst.HOME_BOOK_LIST_3;
+        String key4 = RedisKeyConst.HOME_BOOK_LIST_4;
+        String hashKey = RedisKeyConst.BOOK_HASH;
         listOperations.rightPushAll(key0, bookIdList0);
         listOperations.rightPushAll(key1, bookIdList1);
         listOperations.rightPushAll(key2, bookIdList2);
         listOperations.rightPushAll(key3, bookIdList3);
         listOperations.rightPushAll(key4, bookIdList4);
-
-        String hashKey = "book:bookHash";
         hashOperations.putAll(hashKey, homeBookMap);
     }
 
@@ -152,12 +152,12 @@ public class HomeBookServiceImpl extends ServiceImpl<HomeBookMapper, HomeBook> i
         HashOperations hashOperations = redisTemplate.opsForHash();
 
         // 对应5个类型的数据  比如homeBookList0 对应home_book表中type=0
-        String key0 = "book:homeBookList0";
-        String key1 = "book:homeBookList1";
-        String key2 = "book:homeBookList2";
-        String key3 = "book:homeBookList3";
-        String key4 = "book:homeBookList4";
-        String hashKey = "book:bookHash";
+        String key0 = RedisKeyConst.HOME_BOOK_LIST_0;
+        String key1 = RedisKeyConst.HOME_BOOK_LIST_1;
+        String key2 = RedisKeyConst.HOME_BOOK_LIST_2;
+        String key3 = RedisKeyConst.HOME_BOOK_LIST_3;
+        String key4 = RedisKeyConst.HOME_BOOK_LIST_4;
+        String hashKey = RedisKeyConst.BOOK_HASH;
         List<String> bookIdList0 = listOperations.range(key0, 0, -1);
         List<String> bookIdList1 = listOperations.range(key1, 0, -1);
         List<String> bookIdList2 = listOperations.range(key2, 0, -1);

+ 83 - 0
novel-demo/src/main/java/com/sf/task/RankTask.java

@@ -0,0 +1,83 @@
+package com.sf.task;
+
+import com.google.gson.Gson;
+import com.sf.cons.RedisKeyConst;
+import com.sf.entity.BookInfo;
+import com.sf.mapper.BookInfoMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.List;
+import java.util.Random;
+
+// 榜单相关的定时任务
+// 设定为组件
+@Component
+@RequiredArgsConstructor
+public class RankTask {
+
+    private final RedisTemplate redisTemplate;
+    private final BookInfoMapper bookInfoMapper;
+    private final Gson gson;
+
+    // 进行任务的调度
+    // 参数是cron表达式  是使用一个字符串来描述要定期执行的任务
+    // https://cron.ciding.cc/  cron表达式生成器
+    // 生成一个每5s执行一次的  0/5 * * * * ? * 要将最后一个*去掉
+    // 生成一个每天凌晨执行的   0 0 0 1/1 * ?
+    @Scheduled(cron = "0 0 0 1/1 * ?")
+    public void updateRank() {
+        System.out.println("updateRank " + LocalDateTime.now());
+        // 可以三个榜单都更新
+        String key = RedisKeyConst.VISIT_ZSET;
+        redisTemplate.delete(key);
+
+        List<BookInfo> bookInfos = bookInfoMapper.selectList(null);
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        for (BookInfo bookInfo : bookInfos) {
+            Long visitCount = bookInfo.getVisitCount();
+            String bookInfoJson = gson.toJson(bookInfo);
+            zSetOperations.add(key, bookInfoJson, visitCount);
+        }
+
+        // 更新新书榜
+        String key2 = RedisKeyConst.NEW_ZSET;
+        redisTemplate.delete(key2);
+        for (BookInfo bookInfo : bookInfos) {
+            LocalDateTime createTime = bookInfo.getCreateTime();
+            long epochMilli;
+            if (createTime != null) {
+                epochMilli = createTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
+            } else {
+                epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+                Random random = new Random();
+                epochMilli += random.nextInt(100);
+            }
+            String json = gson.toJson(bookInfo);
+            zSetOperations.add(key2, json, epochMilli);
+        }
+
+        // 更新 书籍更新榜
+        String key3 = RedisKeyConst.UPDATE_ZSET;
+        redisTemplate.delete(key3);
+        for (BookInfo bookInfo : bookInfos) {
+            LocalDateTime updateTime = bookInfo.getUpdateTime();
+            long epochMilli;
+            if (updateTime != null) {
+                epochMilli = updateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
+            } else {
+                epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+                Random random = new Random();
+                epochMilli += random.nextInt(100);
+            }
+            String json = gson.toJson(bookInfo);
+            zSetOperations.add(key3, json, epochMilli);
+        }
+
+    }
+}

+ 75 - 4
novel-demo/src/test/java/com/sf/BookDataTests.java

@@ -1,6 +1,7 @@
 package com.sf;
 
 import com.google.gson.Gson;
+import com.sf.cons.RedisKeyConst;
 import com.sf.dto.resp.HomeBookRespDto;
 import com.sf.entity.BookInfo;
 import com.sf.entity.HomeBook;
@@ -12,11 +13,11 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.redis.core.HashOperations;
 import org.springframework.data.redis.core.ListOperations;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.*;
 
 @SpringBootTest
 public class BookDataTests {
@@ -119,4 +120,74 @@ public class BookDataTests {
         }
         System.out.println(homeBookRespDtoList);
     }
+
+    @Test
+    public void testBook2() {
+        List<BookInfo> bookInfos = bookInfoMapper.selectList(null);
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        String key = RedisKeyConst.NEW_ZSET;
+        for (BookInfo bookInfo : bookInfos) {
+            LocalDateTime createTime = bookInfo.getCreateTime();
+            long epochMilli;
+            if (createTime != null) {
+                epochMilli = createTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
+            } else {
+                epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+                Random random = new Random();
+                epochMilli += random.nextInt(100);
+            }
+            // 和计算机元年 1970 相比较得到的数值
+//            System.out.println(System.currentTimeMillis());
+            String json = gson.toJson(bookInfo);
+            zSetOperations.add(key, json, epochMilli);
+        }
+    }
+
+    @Test
+    public void testBook3() {
+        List<BookInfo> bookInfos = bookInfoMapper.selectList(null);
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        String key = RedisKeyConst.UPDATE_ZSET;
+        for (BookInfo bookInfo : bookInfos) {
+            LocalDateTime updateTime = bookInfo.getUpdateTime();
+            long epochMilli;
+            if (updateTime != null) {
+                epochMilli = updateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
+            } else {
+                epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+                Random random = new Random();
+                epochMilli += random.nextInt(100);
+            }
+            // 和计算机元年 1970 相比较得到的数值
+//            System.out.println(System.currentTimeMillis());
+            String json = gson.toJson(bookInfo);
+            zSetOperations.add(key, json, epochMilli);
+        }
+    }
+
+    @Test
+    public void testDate() {
+        // 获取秒数
+        long epochSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
+        System.out.println(epochSecond);
+        // 获取毫秒数
+        long epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+        System.out.println(epochMilli);
+        System.out.println(System.currentTimeMillis());
+    }
+
+    @Test
+    public void updateRank(){
+        // 可以三个榜单都更新
+        String key = RedisKeyConst.VISIT_ZSET;
+        redisTemplate.delete(key);
+
+        List<BookInfo> bookInfos = bookInfoMapper.selectList(null);
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        for (BookInfo bookInfo : bookInfos) {
+            Long visitCount = bookInfo.getVisitCount();
+            String bookInfoJson = gson.toJson(bookInfo);
+            zSetOperations.add(key, bookInfoJson, visitCount);
+        }
+    }
 }