7.16热帖排行

帖子的热度就是分数,
不同网站分数记录公式:
在这里插入图片描述
P是关注数,T是帖子发布到现在的时间间隔,按小时计。G是系数,1.5,1.8…

-1,+2这些都是经验值。

log的作用是使得前期的评论、赞、收藏 占比更高,而不是后期。

分数是和发布时间成反比的。

通常启动一个定时任务来算分数(半个小时、一个小时),算完一回,热门的帖子会保持这段时间内的稳定,过段时间会在刷新一遍。

先把评论、赞、收藏 产生变化 的帖子丢进缓存redis中,当时间到了,只计算产生变化的帖子的分数。

RedisKeyUtil

private static final String PREFIX_POST = "post";//存变化的帖子

发帖的时候

    // 统计帖子分数
    public static String getPostScoreKey() {
        return PREFIX_POST + SPLIT + "score";
    }

}

DiscussPostController中

@Autowired
private RedisTemplate redisTemplate;
 // 计算帖子分数
    String redisKey = RedisKeyUtil.getPostScoreKey();//获取key
    redisTemplate.opsForSet().add(redisKey, post.getId());//队列不允许重复数据出现?????我们要去重且不关注顺序 用set,set不允许重复数据存在,会将重复的数据剔除。

置顶不需要加分,只要置顶就是最顶端。

加精的时候需要

// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, id);

删除的时候不需要

还需要处理 评论、点赞时候的处理。

在CommentController中

@Autowired
private RedisTemplate redisTemplate;
  if (comment.getEntityType() == ENTITY_TYPE_POST) { 当评论类型是帖子
   // 计算帖子分数
    String redisKey = RedisKeyUtil.getPostScoreKey();
    redisTemplate.opsForSet().add(redisKey, discussPostId);
}

在LikeController中,
对帖子点赞,才会计算分数

 if(entityType == ENTITY_TYPE_POST) {
            // 计算帖子分数
            String redisKey = RedisKeyUtil.getPostScoreKey();
            redisTemplate.opsForSet().add(redisKey, postId);
        }

项目汇总但凡能影响帖子分数的地方 都把帖子id放入集合set

下面要做的是 每隔一段时间 来统计分数,需要用到上节课所讲的quarze

先写个job
新建PostScoreRefreshJob类。执行帖子分数刷新 的任务


public class PostScoreRefreshJob implements Job, CommunityConstant {

    private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class);//实例化Logger

    //注入一些常用的bean
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DiscussPostService discussPostService;

    @Autowired
    private LikeService likeService;

    @Autowired
    private ElasticsearchService elasticsearchService;

    // 牛客纪元(牛客成立的日子)
    private static final Date epoch;//日期类型是Date
//初始化常量epoch
    static {
        try {
            epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-08-01 00:00:00");//SimpleDateFormat把字符串转为日期,并将其赋值给epoch
        } catch (ParseException e) {
            throw new RuntimeException("初始化牛客纪元失败!", e);
        }
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        String redisKey = RedisKeyUtil.getPostScoreKey();//从redis中取值
        BoundSetOperations operations = redisTemplate.boundSetOps(redisKey);//因为每一个key都要进行运算,即需要反复操作,所以需要BoundSetOperations

        if (operations.size() == 0) {
            logger.info("[任务取消] 没有需要刷新的帖子!");//只需记录日志,不做任何处理
            return;
        }

        logger.info("[任务开始] 正在刷新帖子分数: " + operations.size());//operations.size()表示 要刷新多少个帖子
        while (operations.size() > 0) {//只要redis中有数据,则开始计算
            this.refresh((Integer) operations.pop());//operations是集合
        }
        logger.info("[任务结束] 帖子分数刷新完毕!");
    }

    private void refresh(int postId) {
        DiscussPost post = discussPostService.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;//(wonderful ? 75 : 0) 如果是精华,加75分;
        // 分数 = 帖子权重 + 距离天数
        double score = Math.log10(Math.max(w, 1))//因为log10()里面必须大于1,不然就是负数。
                + (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24);//.getTime()均得到一个毫秒值,方便相减
        // 更新帖子分数
        discussPostService.updateScore(postId, score);
        // 同步搜索数据
        post.setScore(score);
        elasticsearchService.saveDiscussPost(post);
    }

}

在DiscussPostService中,添加

public int updateScore(int id, double score) {
    return discussPostMapper.updateScore(id, score);
}

在discussPostMapper.xml中

<update id="updateScore">
    update discuss_post set score = #{score} where id = #{id}
</update>

该job若想正常运行,还得配置。
在QuartzConfig中:

// 刷新帖子分数任务
@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;
}

@Bean
public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) {
    SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
    factoryBean.setJobDetail(postScoreRefreshJobDetail);
    factoryBean.setName("postScoreRefreshTrigger");
    factoryBean.setGroup("communityTriggerGroup");
    factoryBean.setRepeatInterval(1000 * 60 * 5);
    factoryBean.setJobDataMap(new JobDataMap());
    return factoryBean;
}

测试:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
看看三个帖子的分数,因为做了对数运算,所以差距不大
在这里插入图片描述
接下来做一个展现,
在这里插入图片描述
最热 按照分数对帖子进行排列
因此要对之前的代码重构,使得其支持这种排序。

重构需要从数据访问层改起。
在DiscussPostMapper.XML中

    <select id="selectDiscussPosts" resultType="DiscussPost">
        select <include refid="selectFields"></include>
        from discuss_post
        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, create_time desc <!-- 优先按照type,将置顶的放在前面。其次安装score倒序,其次安照创建时间倒序-->
        </if>
        limit #{offset}, #{limit}
    </select>

我们搜一下,查询哪个地方要调用其
在这里插入图片描述
这里都要进行修改
在这里插入图片描述

在这里插入图片描述
都改为:
在这里插入图片描述
在HomeController中

@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model, Page page,
                           @RequestParam(name = "orderMode", defaultValue = "0") int orderMode) {//点击"最热",则orderMode1,如果没有传进来,则默认为0
    // 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.
    // 所以,在thymeleaf中可以直接访问Page对象中的数据.
    page.setRows(discussPostService.findDiscussPostRows(0));
    page.setPath("/index?orderMode=" + orderMode);

    List<DiscussPost> list = discussPostService
            .findDiscussPosts(0, page.getOffset(), page.getLimit(), orderMode);
    List<Map<String, Object>> discussPosts = new ArrayList<>();
    if (list != null) {
        for (DiscussPost post : list) {
            Map<String, Object> map = new HashMap<>();
            map.put("post", post);
            User user = userService.findUserById(post.getUserId());
            map.put("user", user);

            long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
            map.put("likeCount", likeCount);

            discussPosts.add(map);
        }
    }
    model.addAttribute("discussPosts", discussPosts);
    model.addAttribute("orderMode", orderMode);

    return "/index";
}

在index.html中

<!-- 筛选条件 -->
<ul class="nav nav-tabs mb-3">
	<li class="nav-item">
		<a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新</a><!-- orderMode==0时 则电亮,即加active,否则什么都不输出-->
	</li>
	<li class="nav-item">
		<a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">最热</a>
	</li>
</ul>

测试:
默认 最新排序
在这里插入图片描述
点击最热:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值