# Part07-文章阅读缓存访问量

blog
├─src
│  └─main
│      ├─java
│      │  └─org
│      │      └─myslayers
│      │          │  Application.java
│      │          │
│      │          ├─controller
│      │          │      BaseController.java
│      │          │      PostController.java
│      │          │
│      │          ├─service
│      │          │  │  PostService.java
│      │          │  │
│      │          │  └─impl
│      │          │         PostServiceImpl.java
│      │          ├
│      │          ├─schedules
│      │          │      ViewCountSyncTask.java

# 7.1 数据一致性

  • PostController.java :控制层,【文章阅读【缓存实现访问量】,减少访问数据库的次数,存在一个 BUG,只与点击链接的次数相关,没有与用户的 id 进行绑定】
@Controller
public class PostController extends BaseController {
    /**
     * 详情detail
     */
    @RequestMapping("/post/{id:\\d*}")
    public String detail(@PathVariable(name = "id") long id) {
        /**
         * 一条(post实体类、PostVo实体类)
         */
        //一条:selectOnePost(表 文章id = 传 文章id),因为Mapper中select信息中,id过多引起歧义,故采用p.id
        PostVo postVo = postService.selectOnePost(new QueryWrapper<Post>().eq("p.id", id));
        //req:PostVo实体类 -> CategoryId属性
        req.setAttribute("currentCategoryId", postVo.getCategoryId());
        //req:PostVo实体类(回调)
        req.setAttribute("postVoData", postVo);

        /**
         * 评论(comment实体类)
         */
        //评论:page(分页信息、文章id、用户id、排序)
        IPage<CommentVo> results = commentService.selectComments(getPage(), postVo.getId(), null, "created");
        //req:CommentVo分页集合
        req.setAttribute("commentVoDatas", results);

        /**
         * 文章阅读【缓存实现访问量】:减少访问数据库的次数,存在一个BUG,只与点击链接的次数相关,没有与用户的id进行绑定
         */
        postService.putViewCount(postVo);

        return "post/detail";
    }
}
  • PostServiceImpl.java :业务层实现
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {
    @Autowired
    RedisUtil redisUtil;

    /**
     * 文章阅读【缓存实现访问量】:减少访问数据库的次数,存在一个BUG,只与点击链接的次数相关,没有与用户的id进行绑定
     */
    @Override
    public void putViewCount(PostVo postVo) {
        //1.从缓存中获取当前访问量viewCount
        String hKey = "day:rank:post:" + postVo.getId();
        Integer viewCount = (Integer)redisUtil.hget(hKey, "post-viewCount");

        //2.若缓存中存在viewCount,则viewCount+1;若不存在,则postVo.getViewCount()+1
        //  注意一点,项目启动前会对【7天内的文章】进行缓存,因此,还会存在【7天前的文章】未进行缓存
        if (viewCount != null) {
            postVo.setViewCount(viewCount + 1);
        } else {
            postVo.setViewCount(postVo.getViewCount() + 1);
        }

        //3.将viewCount同步到缓存中
        redisUtil.hset(hKey, "post-viewCount", postVo.getViewCount());
    }
}

# 7.2 定时器定时更新

  • Application.java:项目启动,【每分钟同步一次(缓存 -> 同步到数据库)】
@EnableScheduling//开启定时器
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("http://localhost:8080");
    }
}
  • ViewCountSyncTask.java :定时器
/**
 * 定时器定时更新
 */
@Component
public class ViewCountSyncTask {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    PostService postService;

    //每分钟同步一次(缓存 -> 同步到数据库)
    @Scheduled(cron = "0/5 * * * * *")
    public void task() {
        //1.查询缓存中"day:rank:post:"的全部key
        Set<String> keys = redisTemplate.keys("day:rank:post:" + "*");

        //2.遍历全部key,如果某个key中含有“post-viewCount”,则通过ArrayList数组依次将【带有post-viewCount的文章postId】存放
        List<String> ids = new ArrayList<>();
        for (String key : keys) {
            if (redisUtil.hHasKey(key, "post-viewCount")) {
                String postId = key.substring("day:rank:post:".length());
                ids.add(postId);
            }
        }

        //3.将【全部缓存中的postId】同步到数据库
        if (ids.isEmpty()) {
            //3.1 如果[没有需要更新阅读量的文章】,则直接返回
            return;
        } else {
            //3.2 如果[存在需要更新阅读量的文章】,则先【根据ids查询全部的文章】,再【从缓存中获取该postId对应的访问量】,然后【给Post重新赋值viewCount】
            List<Post> posts = postService.list(new QueryWrapper<Post>().in("id", ids));
            for (Post post : posts) {
                Integer viewCount = (Integer) redisUtil.hget("day:rank:post:" + post.getId(), "post-viewCount");
                post.setViewCount(viewCount);
            }

            //3.3 同步操作
            if (posts.isEmpty()) {
                //如果【数据库中刚好删除完全部文章,即不存在文章】,则直接返回
                return;
            } else {
                //同步数据,并删除缓存
                if (postService.updateBatchById(posts)) {
                    for (String id : ids) {
                        redisUtil.hdel("day:rank:post:" + id, "post-viewCount");
                        System.out.println(id + "---------------------->同步成功");
                    }
                }
            }
        }
    }
}
Last Updated: 6/6/2024, 7:53:07 AM