仿牛客社区——7.16热帖排行

 发帖、对帖子点赞、评论、加精时会产生分数,根据分数对帖子进行排名

(设置定时任务,每隔一段时间自动计算分数)

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

发布帖子

@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,"发布成功!");
	}

给帖子加精

//加精
	@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);
	}

点赞

@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);
	}

重构首页代码


    //定义处理请求的方法
     //若返回的是一个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>

重构查询帖子代码

 <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>

猜你喜欢

转载自blog.csdn.net/zssxcj/article/details/131379741