RankService.java 12.1 KB
Newer Older
刘基明's avatar
刘基明 committed
1 2
package com.tanpu.community.service;

张辰's avatar
张辰 committed
3
import com.tanpu.biz.common.enums.clue.PageEnum;
张辰's avatar
张辰 committed
4 5
import com.tanpu.biz.common.enums.community.CollectionTypeEnum;
import com.tanpu.biz.common.enums.community.TopicStatusEnum;
刘基明's avatar
刘基明 committed
6
import com.tanpu.community.api.beans.qo.ThemeAnalysDO;
刘基明's avatar
刘基明 committed
7
import com.tanpu.community.api.beans.qo.TopicRankQo;
8
import com.tanpu.community.api.beans.vo.feign.fatools.UserInfoResp;
9
import com.tanpu.community.cache.RedisCache;
刘基明's avatar
刘基明 committed
10 11
import com.tanpu.community.dao.entity.community.ThemeEntity;
import com.tanpu.community.dao.entity.community.TopicEntity;
刘基明's avatar
刘基明 committed
12
import com.tanpu.community.util.BizUtils;
刘基明's avatar
刘基明 committed
13
import com.tanpu.community.util.ConvertUtil;
刘基明's avatar
刘基明 committed
14
import com.tanpu.community.util.TimeUtils;
刘基明's avatar
刘基明 committed
15
import org.apache.commons.collections4.CollectionUtils;
16
import org.apache.commons.lang3.StringUtils;
刘基明's avatar
刘基明 committed
17
import org.springframework.beans.factory.annotation.Autowired;
刘基明's avatar
刘基明 committed
18
import org.springframework.beans.factory.annotation.Value;
刘基明's avatar
刘基明 committed
19 20
import org.springframework.stereotype.Service;

21
import javax.annotation.Resource;
刘基明's avatar
刘基明 committed
22 23 24 25 26
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
刘基明's avatar
刘基明 committed
27 28
import java.util.stream.Collectors;

刘基明's avatar
刘基明 committed
29
import static com.tanpu.community.api.constants.RedisKeyConstant.CACHE_FEIGN_USER_INFO;
30

刘基明's avatar
刘基明 committed
31 32
@Service
public class RankService {
刘基明's avatar
刘基明 committed
33

刘基明's avatar
刘基明 committed
34 35 36 37 38 39
    @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;
刘基明's avatar
刘基明 committed
40 41
    @Value("${rank.theme.likeRate:3}")
    public Double likeRate;
刘基明's avatar
刘基明 committed
42 43 44 45 46 47
    @Value("${rank.theme.collectRate:3}")
    public Double collectRate;
    //用户质量权重
    @Value("${rank.theme.userWeightRate:0.9}")
    public Double userWeightRate;
    //初始质量
刘基明's avatar
刘基明 committed
48 49
    @Value("${rank.theme.initialWeight:1.0}")
    public Double initialWeight;
刘基明's avatar
刘基明 committed
50
    //时间系数
刘基明's avatar
刘基明 committed
51
    @Value("${rank.theme.timeRate:0.2}")
刘基明's avatar
刘基明 committed
52 53 54 55
    public Double timeRate;


    @Value("${rank.topic.viewRate:1}")
刘基明's avatar
刘基明 committed
56
    public Double topicViewRate;
刘基明's avatar
刘基明 committed
57
    @Value("${rank.topic.discussRate:3}")
刘基明's avatar
刘基明 committed
58 59 60
    public Double topicDiscussRate;
    @Value("${rank.topic.themeRate:1}")
    public Double topicThemeRate;
刘基明's avatar
刘基明 committed
61
    //时间系数
刘基明's avatar
刘基明 committed
62 63
    @Value("${rank.theme.timeRate:1}")
    public Double topicTimeRate;
刘基明's avatar
刘基明 committed
64

刘基明's avatar
刘基明 committed
65 66 67 68 69 70 71 72 73
    @Autowired
    private ThemeService themeService;
    @Autowired
    private CollectionService collectionService;
    @Autowired
    private CommentService commentService;
    @Autowired
    private TopicService topicService;
    @Autowired
刘基明's avatar
刘基明 committed
74
    private VisitLogService visitLogService;
刘基明's avatar
刘基明 committed
75

张辰's avatar
张辰 committed
76 77 78
    @Autowired
    private FeignService feignService;

79 80 81 82
    @Autowired
    private RedisCache redisCache;

    @Resource
刘基明's avatar
刘基明 committed
83
    private RankLogService rankLogService;
84

85
    //最热
张辰's avatar
张辰 committed
86
    private List<ThemeAnalysDO> hotestThemes = new ArrayList<>();
刘基明's avatar
刘基明 committed
87

刘基明's avatar
刘基明 committed
88 89
    private List<TopicRankQo> rankTopicList = new ArrayList<>();
    private List<TopicRankQo> rankTopicListTop4 = new ArrayList<>();
刘基明's avatar
刘基明 committed
90 91


刘基明's avatar
刘基明 committed
92 93 94
    /**
     * 计算主题热度排行
     */
刘基明's avatar
刘基明 committed
95
    public void rankThemes() {
刘基明's avatar
刘基明 committed
96 97

        LocalDateTime start = LocalDateTime.now();
98
        //7天内所有主题进行热度值排序
刘基明's avatar
刘基明 committed
99
        List<ThemeEntity> themeEntities = themeService.queryRecentdays(60);
刘基明's avatar
刘基明 committed
100
        if (CollectionUtils.isEmpty(themeEntities)) {
刘基明's avatar
刘基明 committed
101 102
            return;
        }
刘基明's avatar
刘基明 committed
103
        List<ThemeAnalysDO> themeAnalysDOS = ConvertUtil.themeEntityToAnalysDOs(themeEntities);
刘基明's avatar
刘基明 committed
104 105 106 107 108 109
        //批量查询
        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);
张辰's avatar
张辰 committed
110
        Map<String, Integer> visitCountMap = visitLogService.getCountMapByTargetIds(themeIds, PageEnum.COMM_VISIT_THEME.getId());
刘基明's avatar
刘基明 committed
111

刘基明's avatar
刘基明 committed
112 113
        for (ThemeAnalysDO theme : themeAnalysDOS) {
            String themeId = theme.getThemeId();
刘基明's avatar
刘基明 committed
114
            theme.setCommentCount(commentCountMap.getOrDefault(themeId, 0));
刘基明's avatar
刘基明 committed
115
            theme.setLikeCount(likeCountMap.getOrDefault(themeId, 0));
刘基明's avatar
刘基明 committed
116 117 118
            theme.setForwardCount(forwardCountMap.getOrDefault(themeId, 0));
            theme.setCollectCount(bookCountMap.getOrDefault(themeId, 0));
            theme.setViewCount(visitCountMap.getOrDefault(themeId, 0));
119 120
            //查询用户质量
            String authorId = theme.getAuthorId();
121 122
            UserInfoResp authorInfo = redisCache.getObject(StringUtils.joinWith("_", CACHE_FEIGN_USER_INFO, authorId),
                    60, () -> feignService.getUserInfoById(authorId), UserInfoResp.class);
123 124 125
            if (authorInfo == null || authorInfo.getLevelGrade() == null) {
                theme.setUserWeight(0.0);
            } else {
张辰's avatar
张辰 committed
126
                // 设置用户权重
127 128 129
                theme.setUserWeight(authorInfo.getLevelGrade() * 1.0);

            }
刘基明's avatar
刘基明 committed
130 131
            //打分
            this.calculateThemeScore(theme);
刘基明's avatar
刘基明 committed
132
        }
刘基明's avatar
刘基明 committed
133
        //排序
刘基明's avatar
刘基明 committed
134

刘基明's avatar
刘基明 committed
135 136 137
        hotestThemes = themeAnalysDOS.stream()
                .sorted(Comparator.comparing(ThemeAnalysDO::getScore).reversed())
                .collect(Collectors.toList());
138 139

        // 落库
刘基明's avatar
刘基明 committed
140 141
        if (redisCache.setIfAbsent("logThemeRank", "1", 60)) {
            rankLogService.logThemeRank(hotestThemes, start, TimeUtils.calMillisTillNow(start));
142 143 144
            redisCache.evict("logThemeRank");
        }

刘基明's avatar
刘基明 committed
145

146 147
    }

148

刘基明's avatar
刘基明 committed
149 150 151 152 153 154
    /**
     * 计算话题热度
     *
     * @return
     */
    public void rankTopics() {
刘基明's avatar
刘基明 committed
155
        LocalDateTime start = LocalDateTime.now();
刘基明's avatar
刘基明 committed
156
        List<TopicEntity> topicEntities = topicService.queryAll();
刘基明's avatar
刘基明 committed
157
        if (CollectionUtils.isEmpty(topicEntities)) {
刘基明's avatar
刘基明 committed
158 159
            this.rankTopicList = new ArrayList<>();
            this.rankTopicListTop4 = new ArrayList<>();
刘基明's avatar
刘基明 committed
160
            return;
刘基明's avatar
刘基明 committed
161
        }
刘基明's avatar
刘基明 committed
162
        List<TopicRankQo> topicRankQos = ConvertUtil.topicEntityToRankQos(topicEntities);
刘基明's avatar
刘基明 committed
163 164

        // 统计话题下的所有主题数据
刘基明's avatar
刘基明 committed
165
        List<String> topicIds = topicRankQos.stream().map(TopicRankQo::getTopicId).collect(Collectors.toList());
166
        Map<String, Integer> topicViewMap = visitLogService.getCountMapByTargetIds(topicIds, PageEnum.COMM_VISIT_TOPIC_DETAIL.getId());
刘基明's avatar
刘基明 committed
167
        for (TopicRankQo topic : topicRankQos) {
刘基明's avatar
刘基明 committed
168
            List<String> themeIds = themeService.queryThemeIdsByTopic(topic.getTopicId());
169
            if (CollectionUtils.isEmpty(themeIds)) {
刘基明's avatar
刘基明 committed
170

刘基明's avatar
刘基明 committed
171
                topic.setViewCount(topicViewMap.getOrDefault(topic.getTopicId(), 0));
刘基明's avatar
刘基明 committed
172
                topic.setDisscussCount(0);
刘基明's avatar
刘基明 committed
173
                topic.setThemeWeight(0.0);
刘基明's avatar
刘基明 committed
174 175 176 177 178 179 180 181 182 183 184 185 186
            } 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);
刘基明's avatar
刘基明 committed
187
            }
188
            // 打分
刘基明's avatar
刘基明 committed
189
            calculateTopicScore(topic);
190
            // 格式化浏览量、讨论量
刘基明's avatar
刘基明 committed
191 192
            topic.setFormatViewCount(BizUtils.formatCountNumber(topic.getViewCount()));
            topic.setFormatDisscussCount(BizUtils.formatCountNumber(topic.getDisscussCount()));
刘基明's avatar
刘基明 committed
193

刘基明's avatar
刘基明 committed
194
        }
刘基明's avatar
刘基明 committed
195

196
        // 排序
刘基明's avatar
刘基明 committed
197 198
        List<TopicRankQo> rankList = topicRankQos.stream()
                .sorted(Comparator.comparing(TopicRankQo::getScore).reversed())
张辰's avatar
张辰 committed
199
                .collect(Collectors.toList());
刘基明's avatar
刘基明 committed
200 201 202 203
        // 非“新”话题才能添加“热”标签
        if (!TopicStatusEnum.NEWEST.getCode().equals(rankList.get(0).getType())) {
            rankList.get(0).setType(TopicStatusEnum.HOTTEST.getCode());
        }
刘基明's avatar
刘基明 committed
204
        this.rankTopicList = rankList;
刘基明's avatar
刘基明 committed
205 206

        // 首页推荐话题
刘基明's avatar
刘基明 committed
207
        // 最新的2个话题
刘基明's avatar
刘基明 committed
208 209
        List<TopicRankQo> newest2Topic = topicRankQos.stream().filter(TopicRankQo::judgeNewTopic)
                .sorted(Comparator.comparing(TopicRankQo::getMinutesTillNow)).limit(2).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
210
        // 最熱的2个话题
刘基明's avatar
刘基明 committed
211 212 213
        List<TopicRankQo> top4Topic = rankList.stream()
                .limit(6)
                .filter(o -> !newest2Topic.contains(o))
刘基明's avatar
刘基明 committed
214
                .limit(4 - newest2Topic.size())
刘基明's avatar
刘基明 committed
215 216
                .collect(Collectors.toList());
        top4Topic.addAll(newest2Topic);
刘基明's avatar
刘基明 committed
217 218 219
        this.rankTopicListTop4 = top4Topic.stream()
                .sorted(Comparator.comparing(TopicRankQo::getScore).reversed())
                .collect(Collectors.toList());
刘基明's avatar
刘基明 committed
220 221

        //落库
刘基明's avatar
刘基明 committed
222 223 224 225
        if (redisCache.setIfAbsent("logTopicRank", "1", 60)) {
            rankLogService.logTopicRank(rankList, start, TimeUtils.calMillisTillNow(start));
            redisCache.evict("logTopicRank");
        }
刘基明's avatar
刘基明 committed
226
        return;
刘基明's avatar
刘基明 committed
227 228
    }

刘基明's avatar
刘基明 committed
229
    /**
刘基明's avatar
刘基明 committed
230
     * 从排序列表中返回话题详情
231
     *
刘基明's avatar
刘基明 committed
232 233 234
     * @param topicId 话题Id
     * @return
     */
235 236
    public TopicRankQo getTopicDetail(String topicId) {
        if (this.rankTopicList.size() == 0) {
刘基明's avatar
刘基明 committed
237 238
            rankTopics();
        }
刘基明's avatar
刘基明 committed
239
        List<TopicRankQo> matchTopic = this.rankTopicList.stream().filter(o -> topicId.equals(o.getTopicId())).limit(1).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
240 241 242 243 244
        matchTopic.add(new TopicRankQo());
        return matchTopic.get(0);
    }


刘基明's avatar
刘基明 committed
245
    public List<TopicRankQo> getRankTopicList(String keyword) {
246
        if (this.rankTopicList.size() == 0) {
刘基明's avatar
刘基明 committed
247 248
            this.rankTopics();
        }
刘基明's avatar
刘基明 committed
249
        if (StringUtils.isEmpty(keyword)) {
刘基明's avatar
刘基明 committed
250
            return rankTopicList;
刘基明's avatar
刘基明 committed
251
        } else {
刘基明's avatar
刘基明 committed
252
            //过滤关键字
刘基明's avatar
刘基明 committed
253
            return this.rankTopicList.stream().filter(o -> o.getTopicTitle().contains(keyword)).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
254
        }
刘基明's avatar
刘基明 committed
255 256 257
    }

    public List<TopicRankQo> getRankTopicListTop4() {
258
        if (this.rankTopicList.size() == 0) {
刘基明's avatar
刘基明 committed
259 260
            this.rankTopics();
        }
刘基明's avatar
刘基明 committed
261 262
        return rankTopicListTop4;
    }
刘基明's avatar
刘基明 committed
263

张辰's avatar
张辰 committed
264 265
    public List<ThemeAnalysDO> getHotestThemes() {
        if (this.hotestThemes.size() == 0) {
266 267
            rankThemes();
        }
张辰's avatar
张辰 committed
268
        return hotestThemes;
269 270
    }

张辰's avatar
张辰 committed
271
    public List<String> getRankThemeListByTopic(String topicId, List<String> excludeIds) {
张辰's avatar
张辰 committed
272
        if (this.hotestThemes.size() == 0) {
刘基明's avatar
刘基明 committed
273 274
            this.rankThemes();
        }
张辰's avatar
张辰 committed
275 276 277

        return hotestThemes.stream()
                .filter(o -> topicId.equals(o.getTopicId()) && !excludeIds.contains(o.getThemeId()))
刘基明's avatar
刘基明 committed
278 279
                .map(ThemeAnalysDO::getThemeId)
                .collect(Collectors.toList());
刘基明's avatar
刘基明 committed
280
    }
刘基明's avatar
刘基明 committed
281

刘基明's avatar
刘基明 committed
282

张辰's avatar
张辰 committed
283 284
    // todo 这里用户层面只考虑了用户的gradelevel,后续可以为用户的历史贴子打分。根据用户发表的历史帖子的质量,为用户评分。例如,历史帖子的关注度,用户的粉丝数量。
    // todo 可以考虑一下话题的热度。
刘基明's avatar
刘基明 committed
285 286 287 288 289
    private void calculateThemeScore(ThemeAnalysDO theme) {
        // 质量=帖子质量+用户质量
        double w = theme.getViewCount() * viewRate
                + theme.getForwardCount() * forwardRate
                + theme.getCommentCount() * commentRate
刘基明's avatar
刘基明 committed
290
                + theme.getLikeCount() * likeRate
刘基明's avatar
刘基明 committed
291 292
                + theme.getCollectCount() * collectRate
                + Math.pow(theme.getUserWeight(), userWeightRate);
293
        double score = (w + initialWeight) / Math.pow(theme.getMinutesTillNow() + 1, timeRate);
刘基明's avatar
刘基明 committed
294 295 296 297 298 299 300 301
        theme.setScore(score);
    }


    public void calculateTopicScore(TopicRankQo topic) {
        //顶置话题
        if (topic.getIsTop() > 0) {
            topic.setScore(Double.MAX_VALUE);
刘基明's avatar
刘基明 committed
302
            return;
刘基明's avatar
刘基明 committed
303
        }
刘基明's avatar
刘基明 committed
304
        Double socre = ((topic.getDisscussCount() * topicDiscussRate + topic.getViewCount() * topicViewRate)
305
                / Math.pow(topic.getMinutesTillNow() + 1, topicTimeRate))
刘基明's avatar
刘基明 committed
306
                + topic.getThemeWeight() * topicThemeRate;
刘基明's avatar
刘基明 committed
307 308
        topic.setScore(socre);
    }
刘基明's avatar
刘基明 committed
309
}