7.7热帖排行功能

一些网站的排行公式:
在这里插入图片描述

开发过程

通常而言,启动一个定时任务进行分数计算,合理的方式:前面的热门帖子保持一定的时间不变,等过段时间定时任务算完在更新。这里为了方便测试,定时任务设置为5min更新一次。
结果的展现:排序的不同
定时计算没必要把所有的帖子都算一遍,把分数变化的帖子丢到一个缓存中,等到定时到了要计算的时候把缓存中的帖子拿出来计算,这样减轻服务器的负担。

RedisKeyUtil

//帖子分数,存的是产生变化的帖子,是多个。不需要传入Id
    public static String getPostScoreKey(){
        return PREFIX_POST + SPLIT + "score";
    }

DiscussPostController

在能影响帖子分数发生变化的地方增加上述的key。

addDiscussPost方法中,在return CommunityUtil.getJSONString(0,“发送成功!”);之前,添加

        //计算帖子的分数,不是立刻算,是放到redis中等定时任务到了才算
        String redisKey = RedisKeyUtil.getPostScoreKey();
        //这里放到set比较好,而不是放到队列中,因为存到队列里会导致重复计算,不关注顺序,能去重的话用set
        redisTemplate.opsForSet().add(redisKey,post.getId());

置顶不需要加分,直接就到最上面了。
加精:

        String redisKey = RedisKeyUtil.getPostScoreKey();
        redisTemplate.opsForSet().add(redisKey,id);

CommentController

评论

        //只有评论给帖子才放到搜索服务器中
        if (comment.getEntityType() == ENTITY_TYPE_COMMENT){
            event = new Event()
                    .setTopic(TOPIC_PUBLISH)
                    .setUserId(comment.getUserId())
                    .setEntityType(ENTITY_TYPE_POST)
                    .setEntityId(discussPostId);

            eventProducer.fireEvent(event);

            //只有评论给帖子才计算分数
            String redisKey = RedisKeyUtil.getPostScoreKey();
            redisTemplate.opsForSet().add(redisKey,discussPostId);

        }

LikeController

点赞

        //只有给帖子点赞才触发计算分数
        if (entityType == ENTITY_TYPE_POST){
            //只有评论给帖子才计算分数
            String redisKey = RedisKeyUtil.getPostScoreKey();
            redisTemplate.opsForSet().add(redisKey,postId);

        }

Quartz定时任务

Job

public class PostScoreRefreshJob implements Job, CommunityConstant {
    //定时任务在关键节点记录一下日志
    private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class);

    @Autowired
    private RedisTemplate redisTemplate;

    //查询帖子、用户,并记录到es中
    @Autowired
    private DiscussPostService discussPostService;

    @Autowired
    private LikeService likeService;

    @Autowired
    private ElasticsearchService elasticsearchService;

    //初始化牛客纪元
    private static final Date epoch;

    static {
        //只需要初始化一次,指定格式转换日期给epoch,后面写的是满足格式的日期
        try {
            epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-08-01 00:00:00");
        } catch (ParseException e) {
            throw new RuntimeException("初始化牛客纪元失败!", e);
        }
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //实现定时任务
        String redisKey = RedisKeyUtil.getPostScoreKey();
        BoundSetOperations operations = redisTemplate.boundSetOps(redisKey);

        if (operations.size() == 0 ){
            logger.info("任务取消,无需要刷新的帖子");
        }

        logger.info("任务开始,正在刷新帖子分数"+operations.size());

        while (operations.size()>0){
            this.refresh((Integer) operations.pop());
        }

        logger.info("任务结束,帖子分数刷新完毕");
    }

    private void refresh(Integer postId) {
        DiscussPost post = discussPostService.findDiscussPostById(postId);
        if (post == null){
            logger.error("该帖子不存在,可能是操作后被删除了"+postId);
            return;
        }

        //是否精华帖子
        boolean wonderful = post.getStatus() == 1;
        //评论数量
        int commentCount = post.getCommentCount();
        //点赞数量
        long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST,postId);

        //计算分数的公式
        // 计算权重,如果是精华,就固定加75的权重值
        double w = (wonderful?75:0)+commentCount*10+likeCount*2;
        //分数=log10(帖子权重,要保证w不小于1,不然会负数,所以有个max)+以天为单位计算距离时间
        double score = Math.log10(Math.max(w,1))
                +(post.getCreateTime().getTime()-epoch.getTime())/(1000*3600*24); //毫秒相减,再转化为天
        //更新帖子分数
        discussPostService.updateScore(postId,score);
        
        //同步搜索数据
        post.setScore(score);
        elasticsearchService.saveDiscussPost(post);
    }
}

discusspostMapper新增updateScore方法

mapper->mapper.xml->service

Quartz中更新配置

    //刷新帖子分数的任务
    //先配置JobDetail 任务详情
    @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;
    }
    
    //然后配置Trigger 触发器
    @Bean
    public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail){
        //Trigger依赖于JobDetail
        SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
        factoryBean.setJobDetail(postScoreRefreshJobDetail);//对那个JobDetail进行设置,spring会优先注入同名的
        factoryBean.setName("PostScoreRefreshTrigger");
        factoryBean.setGroup("communityTriggerGroup");
        factoryBean.setRepeatInterval(1000*60*5);//多长时间执行 5min
        factoryBean.setJobDataAsMap(new JobDataMap()); //底层要用对象来存,存job的状态

        return factoryBean;
    }

首页最热帖子排行

代码重构,支持两种模式orderMode
mapper中:

    List<DiscussPost> selectDiscussPosts(int userId,int offset,int limit,int orderMode);
mapper.xml:
    <select id="selectDiscussPosts" resultType="DiscussPost">
        select <include refid="selectFields"></include>
        from discuss_post
--         如果状态为2(删除状态的)就不能传入
        where status != 2
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
        <if test="orderMode==0">
            order by type desc ,create_time desc
        </if>
        <if test="orderMode==1">
            order by type desc ,score desc
        </if>
         limit #{offset},#{limit}
    </select>

前面调用的地方都得改

    public List<DiscussPost> findDiscussPosts(int userId,int offset,int limit,int orderMode){
        return discussPostMapper.selectDiscussPosts(userId,offset,limit,orderMode);
    }

homeController

 public String getIndexPage(Model model, Page page,
                               @RequestParam(name = "orderMode",defaultValue = "0") int orderMode){
        List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit(),orderMode);
        page.setPath("/index?orderMode="+orderMode); //在这里把order传进去,不然分页的参数就没有orderMode了

前端HTML

<li class="nav-item">
	<a th:class="|nav-link ${orderMode==0?'active':''}|"
	 th:href="@{/index(orderMode=0)}">最新</a>
</li>
<li class="nav-item">
	<a th:class="|nav-link ${orderMode==1?'active':''}|"
	 th:href="@{/index(orderMode=1)}">最热</a>
</li>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值