7.7热帖排行功能
一些网站的排行公式:

开发过程
通常而言,启动一个定时任务进行分数计算,合理的方式:前面的热门帖子保持一定的时间不变,等过段时间定时任务算完在更新。这里为了方便测试,定时任务设置为5min更新一次。
结果的展现:排序的不同
定时计算没必要把所有的帖子都算一遍,把分数变化的帖子丢到一个缓存中,等到定时到了要计算的时候把缓存中的帖子拿出来计算,这样减轻服务器的负担。
RedisKeyUtil
//帖子分数,存的是产生变化的帖子,是多个。不需要传入Idpublic static String getPostScoreKey(){return PREFIX_POST + SPLIT + "score";}
DiscussPostController
在能影响帖子分数发生变化的地方增加上述的key。
addDiscussPost方法中,在return CommunityUtil.getJSONString(0,“发送成功!”);之前,添加
//计算帖子的分数,不是立刻算,是放到redis中等定时任务到了才算String redisKey = RedisKeyUtil.getPostScoreKey();//这里放到set比较好,而不是放到队列中,因为存到队列里会导致重复计算,不关注顺序,能去重的话用setredisTemplate.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);@Autowiredprivate RedisTemplate redisTemplate;//查询帖子、用户,并记录到es中@Autowiredprivate DiscussPostService discussPostService;@Autowiredprivate LikeService likeService;@Autowiredprivate 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);}}@Overridepublic 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 任务详情@Beanpublic 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 触发器@Beanpublic SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail){//Trigger依赖于JobDetailSimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();factoryBean.setJobDetail(postScoreRefreshJobDetail);//对那个JobDetail进行设置,spring会优先注入同名的factoryBean.setName("PostScoreRefreshTrigger");factoryBean.setGroup("communityTriggerGroup");factoryBean.setRepeatInterval(1000*60*5);//多长时间执行 5minfactoryBean.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>
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
