package com.tanpu.community.service; import com.tanpu.biz.common.enums.clue.PageEnum; import com.tanpu.biz.common.enums.community.CollectionTypeEnum; import com.tanpu.biz.common.enums.community.TopicStatusEnum; import com.tanpu.community.api.beans.qo.ThemeAnalysDO; import com.tanpu.community.api.beans.qo.TopicRankQo; import com.tanpu.community.api.beans.vo.feign.fatools.UserInfoResp; import com.tanpu.community.cache.RedisCache; import com.tanpu.community.dao.entity.community.ThemeEntity; import com.tanpu.community.dao.entity.community.TopicEntity; import com.tanpu.community.util.BizUtils; import com.tanpu.community.util.ConvertUtil; import com.tanpu.community.util.TimeUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.stream.Collectors; @Service public class RankService { @Value("${rank.theme.viewRate:0.1}") public Double viewRate; @Value("${rank.theme.forwardRate:3}") public Double forwardRate; @Value("${rank.theme.commentRate:2}") public Double commentRate; @Value("${rank.theme.likeRate:3}") public Double likeRate; @Value("${rank.theme.collectRate:3}") public Double collectRate; //用户质量权重 @Value("${rank.theme.userWeightRate:0.9}") public Double userWeightRate; //初始质量 @Value("${rank.theme.initialWeight:1.0}") public Double initialWeight; //时间系数 @Value("${rank.theme.timeRate:0.2}") public Double timeRate; @Value("${rank.topic.viewRate:1}") public Double topicViewRate; @Value("${rank.topic.discussRate:3}") public Double topicDiscussRate; @Value("${rank.topic.themeRate:1}") public Double topicThemeRate; //时间系数 @Value("${rank.theme.timeRate:1}") public Double topicTimeRate; @Autowired private ThemeService themeService; @Autowired private CollectionService collectionService; @Autowired private CommentService commentService; @Autowired private TopicService topicService; @Autowired private VisitLogService visitLogService; @Autowired private FeignService feignService; @Autowired private RedisCache redisCache; @Resource private RankLogService rankLogService; //最热 private List<ThemeAnalysDO> hotestThemes = new ArrayList<>(); private List<TopicRankQo> rankTopicList = new ArrayList<>(); private List<TopicRankQo> rankTopicListTop4 = new ArrayList<>(); /** * 计算主题热度排行 */ public void rankThemes() { LocalDateTime start = LocalDateTime.now(); //7天内所有主题进行热度值排序 List<ThemeEntity> themeEntities = themeService.queryRecentdaysOrHasTopic(60); if (CollectionUtils.isEmpty(themeEntities)) { return; } List<ThemeAnalysDO> themeAnalysDOS = ConvertUtil.themeEntityToAnalysDOs(themeEntities); //批量查询 List<String> themeIds = themeAnalysDOS.stream().map(ThemeAnalysDO::getThemeId).collect(Collectors.toList()); Map<String, Integer> likeCountMap = collectionService.getCountMapByType(themeIds, CollectionTypeEnum.LIKE_THEME); Map<String, Integer> bookCountMap = collectionService.getCountMapByType(themeIds, CollectionTypeEnum.COLLECT_THEME); Map<String, Integer> commentCountMap = commentService.getCountMapByThemeIds(themeIds); Map<String, Integer> forwardCountMap = themeService.getForwardCountMap(themeIds); Map<String, Integer> visitCountMap = visitLogService.getCountMapByTargetIds(themeIds, PageEnum.COMM_VISIT_THEME.getId()); for (ThemeAnalysDO theme : themeAnalysDOS) { String themeId = theme.getThemeId(); theme.setCommentCount(commentCountMap.getOrDefault(themeId, 0)); theme.setLikeCount(likeCountMap.getOrDefault(themeId, 0)); theme.setForwardCount(forwardCountMap.getOrDefault(themeId, 0)); theme.setCollectCount(bookCountMap.getOrDefault(themeId, 0)); theme.setViewCount(visitCountMap.getOrDefault(themeId, 0)); //查询用户质量 String authorId = theme.getAuthorId(); UserInfoResp authorInfo = feignService.getUserInfoById(authorId); if (authorInfo == null || authorInfo.getLevelGrade() == null) { theme.setUserWeight(0.0); } else { // 设置用户权重 theme.setUserWeight(authorInfo.getLevelGrade() * 1.0); } //打分 this.calculateThemeScore(theme); } //排序 hotestThemes = themeAnalysDOS.stream() .sorted(Comparator.comparing(ThemeAnalysDO::getScore).reversed()) .collect(Collectors.toList()); // 落库 if (redisCache.setIfAbsent("logThemeRank", "1", 60)) { rankLogService.logThemeRank(hotestThemes, start, TimeUtils.calMillisTillNow(start)); redisCache.evict("logThemeRank"); } } /** * 计算话题热度 * * @return */ public void rankTopics() { LocalDateTime start = LocalDateTime.now(); List<TopicEntity> topicEntities = topicService.queryAll(); if (CollectionUtils.isEmpty(topicEntities)) { this.rankTopicList = new ArrayList<>(); this.rankTopicListTop4 = new ArrayList<>(); return; } List<TopicRankQo> topicRankQos = ConvertUtil.topicEntityToRankQos(topicEntities); // 统计话题下的所有主题数据 List<String> topicIds = topicRankQos.stream().map(TopicRankQo::getTopicId).collect(Collectors.toList()); Map<String, Integer> topicViewMap = visitLogService.getCountMapByTargetIds(topicIds, Arrays.asList(PageEnum.COMM_VISIT_TOPIC_DETAIL_HOT.getId(),PageEnum.COMM_VISIT_TOPIC_DETAIL_NEW.getId(),PageEnum.COMM_VISIT_TOPIC_DETAIL.getId())); for (TopicRankQo topic : topicRankQos) { List<String> themeIds = themeService.queryThemeIdsByTopic(topic.getTopicId()); if (CollectionUtils.isEmpty(themeIds)) { topic.setViewCount(topicViewMap.getOrDefault(topic.getTopicId(), 0)); topic.setDisscussCount(0); topic.setThemeWeight(0.0); } else { // 浏览量 Integer topicPV = topicViewMap.getOrDefault(topic.getTopicId(), 0); Integer themePV = visitLogService.queryThemeVisit(themeIds); topic.setViewCount(topicPV + themePV + topic.getViewCntAdjust()); //讨论数=发布主题贴数+回复总数 Integer commentCount = commentService.getTotalCountByThemeIds(themeIds); topic.setDisscussCount(themeIds.size() + commentCount); //帖子权重,求和 double themeSum = getHotestThemes().stream().filter(o -> topic.getTopicId().equals(o.getTopicId())) .mapToDouble(ThemeAnalysDO::getScore) .sum(); topic.setThemeWeight(themeSum); } // 打分 calculateTopicScore(topic); // 格式化浏览量、讨论量 topic.setFormatViewCount(BizUtils.formatCountNumber(topic.getViewCount())); topic.setFormatDisscussCount(BizUtils.formatCountNumber(topic.getDisscussCount())); } // 排序 List<TopicRankQo> rankList = topicRankQos.stream() .sorted(Comparator.comparing(TopicRankQo::getScore).reversed()) .collect(Collectors.toList()); // 非“新”话题才能添加“热”标签 if (!TopicStatusEnum.NEWEST.getCode().equals(rankList.get(0).getType())) { rankList.get(0).setType(TopicStatusEnum.HOTTEST.getCode()); } this.rankTopicList = rankList; // 首页推荐话题 // 最新的2个话题 List<TopicRankQo> newest2Topic = topicRankQos.stream().filter(TopicRankQo::judgeNewTopic) .sorted(Comparator.comparing(TopicRankQo::getMinutesTillNow)).limit(2).collect(Collectors.toList()); // 最熱的2个话题 List<TopicRankQo> top4Topic = rankList.stream() .limit(6) .filter(o -> !newest2Topic.contains(o)) .limit(4 - newest2Topic.size()) .collect(Collectors.toList()); top4Topic.addAll(newest2Topic); this.rankTopicListTop4 = top4Topic.stream() .sorted(Comparator.comparing(TopicRankQo::getScore).reversed()) .collect(Collectors.toList()); //落库 int i = new Random().nextInt(3); if (i==1 && redisCache.setIfAbsent("logTopicRank", "1", 60)) { rankLogService.logTopicRank(rankList, start, TimeUtils.calMillisTillNow(start)); redisCache.evict("logTopicRank"); } } /** * 从排序列表中返回话题详情 * * @param topicId 话题Id * @return */ public TopicRankQo getTopicDetail(String topicId) { if (this.rankTopicList.size() == 0) { rankTopics(); } List<TopicRankQo> matchTopic = this.rankTopicList.stream().filter(o -> topicId.equals(o.getTopicId())).limit(1).collect(Collectors.toList()); matchTopic.add(new TopicRankQo()); return matchTopic.get(0); } public List<TopicRankQo> getRankTopicList(String keyword) { if (this.rankTopicList.size() == 0) { this.rankTopics(); } if (StringUtils.isEmpty(keyword)) { return rankTopicList; } else { //过滤关键字 return this.rankTopicList.stream().filter(o -> o.getTopicTitle().contains(keyword)).collect(Collectors.toList()); } } public List<TopicRankQo> getRankTopicListTop4() { if (this.rankTopicList.size() == 0) { this.rankTopics(); } return rankTopicListTop4; } public List<ThemeAnalysDO> getHotestThemes() { if (this.hotestThemes.size() == 0) { rankThemes(); } return hotestThemes; } public List<String> getRankThemeListByTopic(String topicId, List<String> excludeIds) { if (this.hotestThemes.size() == 0) { this.rankThemes(); } return hotestThemes.stream() .filter(o -> topicId.equals(o.getTopicId()) && !excludeIds.contains(o.getThemeId())) .map(ThemeAnalysDO::getThemeId) .collect(Collectors.toList()); } // todo 这里用户层面只考虑了用户的gradelevel,后续可以为用户的历史贴子打分。根据用户发表的历史帖子的质量,为用户评分。例如,历史帖子的关注度,用户的粉丝数量。 // todo 可以考虑一下话题的热度。 private void calculateThemeScore(ThemeAnalysDO theme) { // 质量=帖子质量+用户质量 double w = theme.getViewCount() * viewRate + theme.getForwardCount() * forwardRate + theme.getCommentCount() * commentRate + theme.getLikeCount() * likeRate + theme.getCollectCount() * collectRate + Math.pow(theme.getUserWeight(), userWeightRate); double score = (w + initialWeight) / Math.pow(theme.getMinutesTillNow() + 1, timeRate); theme.setScore(score); } public void calculateTopicScore(TopicRankQo topic) { //顶置话题 if (topic.getIsTop() > 0) { topic.setScore(Double.MAX_VALUE); return; } Double socre = ((topic.getDisscussCount() * topicDiscussRate + topic.getViewCount() * topicViewRate) / Math.pow(topic.getMinutesTillNow() + 1, topicTimeRate)) + topic.getThemeWeight() * topicThemeRate; topic.setScore(socre); } }