Imitation Niuke Community——7.16 Hot Post Ranking

 Scores will be generated when posting, liking, commenting, and refining posts, and the posts will be ranked according to the scores

(Set up timed tasks and automatically calculate scores at regular intervals)

config

@Configuration
public class QuartzConfig {
	/**
	 * FactoryBean和BeanFactory
	 *
	 * BeanFactory:IOC容器顶层接口
	 *
	 *
	 * FactoryBean:可简化bean的实例化过程
	 * 	1.通过FactoryBean封装了某些bean的实例化过程
	 * 	2.将FactoryBean装配到spring中
	 * 	3.将FactoryBean注入给其他的bean
	 * 	4.该bean得到的是FactoryBean所管理的对象实例
	 *
	 */

	//配置jobDetail
	//刷新帖子分数配置
	@Bean
	public JobDetailFactoryBean  postScoreRefreshDetail(){
		JobDetailFactoryBean factoryBean=new JobDetailFactoryBean(); //实例化对象
		//设置属性
		factoryBean.setJobClass( PostScoreRefreshJob.class);
		factoryBean.setName("postScoreRefreshJob");
		factoryBean.setGroup("communityJobGroup");
		factoryBean.setDurability(true);  //该任务是否长久保存
		factoryBean.setRequestsRecovery(true);  //该任务是否可恢复的
		return factoryBean;
	}

	//配置trigger(依赖于jobDetail,需将jobDetail注入)【SimpleTriggerFactoryBean(简单trigger),CronTriggerFactoryBean(复杂的trigger)】
	@Bean
	public SimpleTriggerFactoryBean  postScoreRefreshTrigger(JobDetail postScoreRefreshDetail){
		SimpleTriggerFactoryBean factoryBean=new SimpleTriggerFactoryBean();
		factoryBean.setJobDetail(postScoreRefreshDetail);
		factoryBean.setName("postScoreRefreshTrigger");
		factoryBean.setGroup("communityTriggerGroup");
		factoryBean.setRepeatInterval(1000*60*5); //时间间隔
		factoryBean.setJobDataMap(new JobDataMap()); //trigger底层需要存储job的一些状态(初始化了一个默认的存储方式
		return factoryBean;
	}
}

quartz

public class PostScoreRefreshJob implements Job , CommunityConstant {

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private LikeService likeService;

	@Autowired
	private DiscussPostService discussPostService;

	@Autowired
	private ElasticsearchService elasticsearchService;

	private static final Logger logger= LoggerFactory.getLogger(PostScoreRefreshJob.class);

	//牛客纪元
	private static final Date epoch;

	static {
		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 jobExecutionContext) throws JobExecutionException {
		String redisKey = RedisKeyUtil.getPostScoreKey();
		///BoundSetOperations就是一个绑定key的对象,我们可以通过这个对象来进行与key相关的操作。
		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()); //根据帖子id查找帖子计算分数
		}

		logger.info("[任务结束] 帖子分数刷新完毕!");

	}

	private void refresh(int postId){


		DiscussPost post = discussPostService.findDiscussPostById(postId);
		if(post==null){
			logger.error("该帖子不存在!");
			return;
		}
		if(post.getStatus()==2){
			logger.error("该帖子已被删除!");
			return;
		}
		//是否精华
		boolean wonderful = post.getStatus() == 1;
		//评论数量
		int commentCount = post.getCommentCount();
		//点赞数量
		long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId);

		//log(精华分+评论数*10+ 点赞数*2) +(发布时间 - 牛客纪元)
		//计算权重
		long w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;
		//分数
		double score = Math.log10(Math.max(1, w)) + (post.getCreateTime().getTime() - epoch.getTime()) / (3600 * 24 * 1000);

		//更新帖子分数
		discussPostService.updateScore(postId,score);

		//同步elasticsearch中搜索数据
		post.setScore(score);
		elasticsearchService.saveDiscussPost(post); //这个实体是早先查到的,中途更改了它的分数,所以需要将分数同步一下


	}
}

controller

post

@RequestMapping(value = "/add",method = RequestMethod.POST)
	@ResponseBody
	public String addDiscussPost(String title,String content){
		//判断是否登录
		User user = hostHolder.getUser();
		if(user==null){
			//还未登陆无权限访问
			return CommunityUtil.getJsonString(403,"还未登陆!");
		}
		DiscussPost post=new DiscussPost();
		post.setUserId(user.getId());
		post.setTitle(title);
		post.setContent(content);
		post.setCreateTime(new Date());
		discussPostService.addDiscussPost(post);

		//发布帖子后,同步到elasticsearch中
		//利用事件进行发送
		Event event=new Event()
				.setTopic(TOPIC_PUBLISH)
				.setUserId(user.getId())
				.setEntityType(ENTITY_TYPE_POST)
				.setEntityId(post.getId());

		eventProducer.fireEvent(event);

		//计算帖子分数(将要计算分数的帖子加入set中
		String redisKey = RedisKeyUtil.getPostScoreKey();
		redisTemplate.opsForSet().add(redisKey,post.getId());


		return CommunityUtil.getJsonString(0,"发布成功!");
	}

refine the post

//加精
	@RequestMapping(value = "/wonderful",method = RequestMethod.POST)
	@ResponseBody //结果返回为json字符串
	public String setWonderful(int id){
		DiscussPost post = discussPostService.findDiscussPostById(id);
		// 1为加精,0为正常, 1^1=0, 0^1=1
		int status = post.getStatus()^1;
		//更改帖子状态
		discussPostService.updateStatus(id,status);
		//将返回的结果
		Map<String,Object> map=new HashMap<>();
		map.put("status",status);


		//更改完帖子后,要同步更新elasticsearch中的结果
		//触发发帖事件
		Event event=new Event()
				.setTopic(TOPIC_PUBLISH)
				.setUserId(hostHolder.getUser().getId())
				.setEntityId(id)
				.setEntityType(ENTITY_TYPE_POST);

		eventProducer.fireEvent(event);
		//计算帖子分数(将要计算分数的帖子加入set中
		String redisKey = RedisKeyUtil.getPostScoreKey();
		redisTemplate.opsForSet().add(redisKey,id);

		return CommunityUtil.getJsonString(0,null,map);
	}

like

@RequestMapping(value = "/like",method = RequestMethod.POST)
	@ResponseBody
	public String like(int entityType,int entityId,int entityUserId,int postId){
		User user = hostHolder.getUser();
		//点赞
		likeService.like(user.getId(),entityType,entityId, entityUserId);

		//获取点赞数量
		long likeCount = likeService.findEntityLikeCount(entityType, entityId);
		//获取点赞状态
		int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
		//返回的结果
		Map<String,Object> map=new HashMap<>();
		map.put("likeCount",likeCount);
		map.put("likeStatus",likeStatus);

		//触发点赞事件
		if(likeStatus==1){
			Event event=new Event()
					.setTopic(TOPIC_LIKE)
					.setUserId(hostHolder.getUser().getId())
					.setEntityId(entityId)
					.setEntityType(entityType)
					.setEntityUserId(entityUserId)
					.setData("postId",postId);

			eventProducer.fireEvent(event);
		}

		//计算对帖子点赞的情况
		if(entityType==ENTITY_TYPE_POST){
			//计算帖子分数(将要计算分数的帖子加入set中
			String redisKey = RedisKeyUtil.getPostScoreKey();
			redisTemplate.opsForSet().add(redisKey,postId);

		}

		return CommunityUtil.getJsonString(0,null,map);
	}

Refactor the home page code


    //定义处理请求的方法
     //若返回的是一个HTML,则不用加@Response注解
    @RequestMapping(path = "/index",method = RequestMethod.GET)
    public String getIndexPage(Model model, Page page, @RequestParam(name = "orderMode",defaultValue = "0")int orderMode){
        //在SpringMVC中,方法参数都是由DispatcherServlet初始化的,
        //还会额外把Page对象装进Model中

        /*
        在方法调用前,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<>();
        for(DiscussPost post:list){
            Map<String,Object> map=new HashMap<>();
            map.put("post",post);
            User user = userService.findUserById(post.getUserId());//再通过获取的帖子信息的UserID找到User完整信息
            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";
    }

service

 //更改帖子分数
    public int updateScore(int id,double score){
        return discussPostMapper.updateScore(id,score);
    }

dao

 //更改帖子分数
    int updateScore(int id,double score);

mapper.xml

<!-- int updateScore(int id,int score); -->
    <update id="updateScore">
        update discuss_post
        set score=#{score}
        where id=#{id}
    </update>

Refactor query post code

 <select id="selectDiscussPost" 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
        </if>

        limit #{offset},#{limit}
    </select>

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>
						</li>
						<li class="nav-item">
							<a th:class="|nav-link  ${orderMode==1?'active':''}|"  th:href="@{/index(orderMode=1)}">最热</a>
						</li>
					</ul>

Guess you like

Origin blog.csdn.net/zssxcj/article/details/131379741