Эх сурвалжийг харах

0724 zset实现点击量排行榜

Qing 10 сар өмнө
parent
commit
f32adbfea9

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

@@ -7,11 +7,13 @@ import com.sf.dto.req.BookSearchReqDto;
 import com.sf.entity.BookInfo;
 import com.sf.service.IBookChapterService;
 import com.sf.service.IBookInfoService;
+import com.sf.service.IBookRankService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.web.bind.annotation.*;
 
 import java.security.NoSuchAlgorithmException;
@@ -34,6 +36,10 @@ public class BookInfoController {
     @Autowired
     private IBookInfoService bookInfoService;
 
+    @Qualifier("bookRankServiceByRedis")
+    @Autowired
+    private IBookRankService bookRankService;
+
     @Autowired
     private IBookChapterService bookChapterService;
 
@@ -85,27 +91,6 @@ public class BookInfoController {
         return RestResp.ok(bookCategoryRespDtos);
     }
 
-    @Operation(summary = "点击量排行榜接口")
-    @GetMapping("/api/front/book/visit_rank")
-    public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
-        List<BookRankRespDto> bookRankRespDtos = bookInfoService.listVisitRankBooks();
-        return RestResp.ok(bookRankRespDtos);
-    }
-
-    @Operation(summary = "新书排行榜接口")
-    @GetMapping("/api/front/book/newest_rank")
-    public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
-        List<BookRankRespDto> bookRankRespDtos = bookInfoService.listNewestRankBooks();
-        return RestResp.ok(bookRankRespDtos);
-    }
-
-    @Operation(summary = "更新排行榜接口")
-    @GetMapping("/api/front/book/update_rank")
-    public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
-        List<BookRankRespDto> bookRankRespDtos = bookInfoService.listUpdateRankBooks();
-        return RestResp.ok(bookRankRespDtos);
-    }
-
 
     // 最新章节请求
     // http://127.0.0.1:8888/api/front/book/last_chapter/about?bookId=1431630596354977794
@@ -120,7 +105,9 @@ public class BookInfoController {
     @PostMapping("/api/front/book/visit")
     public RestResp<Void> addVisitCount(@Parameter(description = "小说ID")
                                         @RequestBody BookVisitReqDto reqDto) {
-        bookInfoService.addVisitCount(Long.parseLong(reqDto.getBookId()));
+        long bookId = Long.parseLong(reqDto.getBookId());
+        bookRankService.updateVisitRank(bookId);
+        bookInfoService.addVisitCount(bookId);
         return RestResp.ok(null);
     }
 

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

@@ -0,0 +1,51 @@
+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;
+
+@Tag(name = "BookRankController", description = "书籍排行榜模块")
+@RestController
+public class BookRankController {
+
+    // 如果有多个实现类 可以通过名字进行对象注入
+    // There is more than one bean of 'IBookRankService' type.
+    @Qualifier("bookRankServiceByRedis")
+//    @Qualifier("bookRankServiceByMysql")
+    @Autowired
+    private IBookRankService bookRankService;
+
+    @Operation(summary = "点击量排行榜接口")
+    @GetMapping("/api/front/book/visit_rank")
+    public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
+        List<BookRankRespDto> bookRankRespDtos = bookRankService.listVisitRankBooks();
+        return RestResp.ok(bookRankRespDtos);
+    }
+
+    @Operation(summary = "新书排行榜接口")
+    @GetMapping("/api/front/book/newest_rank")
+    public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
+        List<BookRankRespDto> bookRankRespDtos = bookRankService.listNewestRankBooks();
+        return RestResp.ok(bookRankRespDtos);
+    }
+
+    @Operation(summary = "更新排行榜接口")
+    @GetMapping("/api/front/book/update_rank")
+    public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
+        List<BookRankRespDto> bookRankRespDtos = bookRankService.listUpdateRankBooks();
+        return RestResp.ok(bookRankRespDtos);
+    }
+
+
+}

+ 0 - 20
novel-demo/src/main/java/com/sf/service/IBookInfoService.java

@@ -39,24 +39,4 @@ public interface IBookInfoService extends IService<BookInfo> {
 
     List<BookInfoRespDto> listRecBooks(Long bookId);
 
-    /**
-     * 小说点击榜查询
-     *
-     * @return 小说点击排行列表
-     */
-    List<BookRankRespDto> listVisitRankBooks();
-
-    /**
-     * 小说新书榜查询
-     *
-     * @return 小说新书排行列表
-     */
-    List<BookRankRespDto> listNewestRankBooks();
-
-    /**
-     * 小说更新榜查询
-     *
-     * @return 小说更新排行列表
-     */
-    List<BookRankRespDto> listUpdateRankBooks();
 }

+ 36 - 0
novel-demo/src/main/java/com/sf/service/IBookRankService.java

@@ -0,0 +1,36 @@
+package com.sf.service;
+
+import com.sf.dto.resp.BookRankRespDto;
+
+import java.util.List;
+
+// 为什么service层都是先写接口 再写实现类
+// 虽然大部分时候 一个接口只有一个实现类 但是一个接口是支持多种实现的
+public interface IBookRankService {
+
+    /**
+     * 小说点击榜查询
+     *
+     * @return 小说点击排行列表
+     */
+    List<BookRankRespDto> listVisitRankBooks();
+
+    /**
+     * 小说新书榜查询
+     *
+     * @return 小说新书排行列表
+     */
+    List<BookRankRespDto> listNewestRankBooks();
+
+    /**
+     * 小说更新榜查询
+     *
+     * @return 小说更新排行列表
+     */
+    List<BookRankRespDto> listUpdateRankBooks();
+
+    /**
+     * 更新排行榜中点击量
+     */
+    void updateVisitRank(Long bookId);
+}

+ 0 - 43
novel-demo/src/main/java/com/sf/service/impl/BookInfoServiceImpl.java

@@ -158,49 +158,6 @@ public class BookInfoServiceImpl extends ServiceImpl<BookInfoMapper, BookInfo>
         return bookInfoRespDtos;
     }
 
-    @Override
-    public List<BookRankRespDto> listVisitRankBooks() {
-        LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper = new LambdaQueryWrapper<>();
-        bookInfoQueryWrapper.orderByDesc(BookInfo::getVisitCount);
-        return listRankBooks(bookInfoQueryWrapper);
-    }
-
-    /**
-     * 查询小说新书榜列表,并放入缓存中
-     */
-    @Override
-    public List<BookRankRespDto> listNewestRankBooks() {
-        LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper = new LambdaQueryWrapper<>();
-        bookInfoQueryWrapper.orderByDesc(BookInfo::getCreateTime);
-        return listRankBooks(bookInfoQueryWrapper);
-    }
 
-    /**
-     * 查询小说更新榜列表,并放入缓存中
-     */
-    @Override
-    public List<BookRankRespDto> listUpdateRankBooks() {
-        LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper = new LambdaQueryWrapper<>();
-        bookInfoQueryWrapper.orderByDesc(BookInfo::getUpdateTime);
-        return listRankBooks(bookInfoQueryWrapper);
-    }
-
-    private List<BookRankRespDto> listRankBooks(LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper) {
-        bookInfoQueryWrapper.gt(BookInfo::getWordCount, 0).last("limit 30");
-        return bookInfoMapper.selectList(bookInfoQueryWrapper).stream().map(v -> {
-            BookRankRespDto respDto = new BookRankRespDto();
-            respDto.setId(v.getId());
-            respDto.setCategoryId(v.getCategoryId());
-            respDto.setCategoryName(v.getCategoryName());
-            respDto.setBookName(v.getBookName());
-            respDto.setAuthorName(v.getAuthorName());
-            respDto.setPicUrl(v.getPicUrl());
-            respDto.setBookDesc(v.getBookDesc());
-            respDto.setLastChapterName(v.getLastChapterName());
-            respDto.setLastChapterUpdateTime(v.getLastChapterUpdateTime());
-            respDto.setWordCount(v.getWordCount());
-            return respDto;
-        }).toList();
-    }
 
 }

+ 83 - 0
novel-demo/src/main/java/com/sf/service/impl/BookRankByRedisServiceImpl.java

@@ -0,0 +1,83 @@
+package com.sf.service.impl;
+
+import com.google.gson.Gson;
+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;
+
+import java.util.List;
+import java.util.Set;
+
+// 基于redis
+@Service("bookRankServiceByRedis")
+@RequiredArgsConstructor
+public class BookRankByRedisServiceImpl implements IBookRankService {
+    // 数据初始化
+    private final BookInfoMapper bookInfoMapper;
+    private final RedisTemplate redisTemplate;
+    private final Gson gson;
+
+    @Override
+    public List<BookRankRespDto> listVisitRankBooks() {
+        // 将书籍对象的json放入zset的value中 点击量放入scores
+        String key = "book:visitZset";
+        // 当zset中已经有值时  只需要从zset中取数据
+        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
+        Long size = zSetOperations.size(key);
+        if (size == 0) {
+            System.out.println("visitZset size:" + size);
+            initData();
+        }
+        Set set = zSetOperations.reverseRange(key, 0, 30);
+        return set.stream().map(obj -> {
+            // 将map中的每一个元素 转化为BookRankRespDto
+            String str = (String) obj;
+            BookInfo bookInfo = gson.fromJson(str, BookInfo.class);
+            BookRankRespDto bookRankRespDto = new BookRankRespDto();
+            BeanUtils.copyProperties(bookInfo, bookRankRespDto);
+            return bookRankRespDto;
+        }).toList();
+    }
+
+    // 数据初始化
+    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);
+        }
+    }
+
+    @Override
+    public List<BookRankRespDto> listNewestRankBooks() {
+        return List.of();
+    }
+
+    @Override
+    public List<BookRankRespDto> listUpdateRankBooks() {
+        return List.of();
+    }
+
+    @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);
+        bookInfo.setVisitCount(bookInfo.getVisitCount() + 1);
+        zSetOperations.add(key, gson.toJson(bookInfo), bookInfo.getVisitCount());
+    }
+}

+ 65 - 0
novel-demo/src/main/java/com/sf/service/impl/BookRankServiceImpl.java

@@ -0,0 +1,65 @@
+package com.sf.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.stereotype.Service;
+
+import java.util.List;
+
+// 基于mysql实现
+@Service("bookRankServiceByMysql")
+@RequiredArgsConstructor
+public class BookRankServiceImpl implements IBookRankService {
+
+    private final BookInfoMapper bookInfoMapper;
+
+    @Override
+    public List<BookRankRespDto> listVisitRankBooks() {
+        LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper = new LambdaQueryWrapper<>();
+        bookInfoQueryWrapper.orderByDesc(BookInfo::getVisitCount);
+        return listRankBooks(bookInfoQueryWrapper);
+    }
+
+    /**
+     * 查询小说新书榜列表,并放入缓存中
+     */
+    @Override
+    public List<BookRankRespDto> listNewestRankBooks() {
+        LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper = new LambdaQueryWrapper<>();
+        bookInfoQueryWrapper.orderByDesc(BookInfo::getCreateTime);
+        return listRankBooks(bookInfoQueryWrapper);
+    }
+
+    /**
+     * 查询小说更新榜列表,并放入缓存中
+     */
+    @Override
+    public List<BookRankRespDto> listUpdateRankBooks() {
+        LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper = new LambdaQueryWrapper<>();
+        bookInfoQueryWrapper.orderByDesc(BookInfo::getUpdateTime);
+        return listRankBooks(bookInfoQueryWrapper);
+    }
+
+    @Override
+    public void updateVisitRank(Long bookId) {
+
+    }
+
+    private List<BookRankRespDto> listRankBooks(LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper) {
+        // 先将不同的排序规则传入 点击量、创建时间、更新时间
+        // 相同的筛选规则是 字数>0 返回30行
+        bookInfoQueryWrapper.gt(BookInfo::getWordCount, 0).last("limit 30");
+        List<BookInfo> bookInfos = bookInfoMapper.selectList(bookInfoQueryWrapper);
+        // 将数据库中的数据 映射成接口要返回的数据dto
+        return bookInfos.stream().map(bookInfo -> {
+            BookRankRespDto respDto = new BookRankRespDto();
+            BeanUtils.copyProperties(bookInfo, respDto);
+            return respDto;
+        }).toList();
+    }
+}