# 热帖排行 --- 每隔一段时间就计算帖子的分数,需要使用分布式定时任务: ![](https://gitee.com/veal98/images/raw/master/img/20210131152028.png) ![](https://gitee.com/veal98/images/raw/master/img/20210131152852.png) Spring Quartz 导包 ```xml org.springframework.boot spring-boot-starter-quartz ``` 由于 Spring Quartz 依赖于数据库,所以我们需要提前在数据库中创建 Quartz 需要的表 运行 init_quartz.sql 文件 分数计算设计: ![](https://gitee.com/veal98/images/raw/master/img/20210131161518.png) ## 分数计算 每次发生点赞(给帖子点赞)、评论(给帖子评论)、加精的时候,就将这些帖子存入缓存 Redis 中,然后通过分布式的定时任务,每隔一段时间就从缓存中取出这些帖子进行计算分数。 RedisKeyUtil ```java /** * 帖子分数 * @return */ public static String getPostScoreKey() { return PREFIX_POST + SPLIT + "score"; } ``` ### Controller 以点赞操作为例: ### Job ```java /** * 帖子分数计算刷新 */ public class PostScoreRefreshJob implements Job, CommunityConstant { private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class); @Autowired private RedisTemplate redisTemplate; @Autowired private DiscussPostSerivce discussPostSerivce; @Autowired private LikeService likeService; @Autowired private ElasticsearchService elasticsearchService; // Epoch 纪元 private static final Date epoch; static { try { epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-01-01 00:00:00"); } catch (ParseException e) { throw new RuntimeException("初始化 Epoch 纪元失败", e); } } @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { String redisKey = RedisKeyUtil.getPostScoreKey(); BoundSetOperations operations = redisTemplate.boundSetOps(redisKey); if (operations.size() == 0) { logger.info("[任务取消] 没有需要刷新的帖子"); return ; } logger.info("[任务开始] 正在刷新帖子分数: " + operations.size()); while (operations.size() > 0) { this.refresh((Integer) operations.pop()); } logger.info("[任务结束] 帖子分数刷新完毕"); } /** * 刷新帖子分数 * @param postId */ private void refresh(int postId) { DiscussPost post = discussPostSerivce.findDiscussPostById(postId); if (post == null) { logger.error("该帖子不存在: id = " + postId); return ; } // 是否加精 boolean wonderful = post.getStatus() == 1; // 评论数量 int commentCount = post.getCommentCount(); // 点赞数量 long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId); // 计算权重 double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2; // 分数 = 权重 + 发帖距离天数 double score = Math.log10(Math.max(w, 1)) + (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24); // 更新帖子分数 discussPostSerivce.updateScore(postId, score); // 同步更新搜索数据 post.setScore(score); elasticsearchService.saveDiscusspost(post); } } ``` 注意同步更新一下搜索 Elasticsearch 服务器的数据 ### Quarz Config ```java /** * Spring Quartz 配置类,用于将数据存入数据库,以后直接从数据库中调用数据 */ @Configuration public class QuartzConfig { /** * 刷新帖子分数任务 * @return */ @Bean public JobDetailFactoryBean postScoreRefreshJobDetail() { JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); factoryBean.setJobClass(PostScoreRefreshJob.class); factoryBean.setName("postScoreRefreshJob"); factoryBean.setGroup("communityJobGroup"); factoryBean.setDurability(true); factoryBean.setRequestsRecovery(true); return factoryBean; } /** * 刷新帖子分数触发器 * @return */ @Bean public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) { SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); factoryBean.setJobDetail(postScoreRefreshJobDetail); factoryBean.setName("postScoreRefreshTrigger"); factoryBean.setGroup("communityTriggerGroup"); factoryBean.setRepeatInterval(1000 * 60 * 5); // 5分钟刷新一次 factoryBean.setJobDataMap(new JobDataMap()); return factoryBean; } } ``` ## 根据分数进行排行显示 重构下分页查询帖子的方法: ```java /** * 分页查询讨论帖信息 * * @param userId 当传入的 userId = 0 时查找所有用户的帖子 * 当传入的 userId != 0 时,查找该指定用户的帖子 * @param offset 每页的起始索引 * @param limit 每页显示多少条数据 * @param orderMode 排行模式(若传入 1, 则按照热度来排序) * @return */ List selectDiscussPosts(int userId, int offset, int limit, int orderMode); ``` 添加一个 orderMode,若传入 1, 则按照热度来排序,传入 0(默认)则按照最新来排序。当然,置顶的帖子不受影响 对应的 service 也需要做相应修改 ```java /** * 分页查询讨论帖信息 * * @param userId 当传入的 userId = 0 时查找所有用户的帖子 * 当传入的 userId != 0 时,查找该指定用户的帖子 * @param offset 每页的起始索引 * @param limit 每页显示多少条数据 * @param orderMode 排行模式(若传入 1, 则按照热度来排序) * @return */ public List findDiscussPosts (int userId, int offset, int limit, int orderMode) { return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode); } ``` 在修改对应的 HomeController ![](https://gitee.com/veal98/images/raw/master/img/20210131175558.png) Get 方法:前端是通过 ? 来传递参数的:比如说`/index?1`,前端 `th:href="@{/index(orderMode=0)}"` 而通过请求体来传递参数使用的是 Post 请求 :比如说`/add/postid`,前端 `th:href="@{|/index/${post.id}|}"` 修改一下 index.html ```html 最新 最热 ```