RecommendService.java 6.9 KB
Newer Older
1 2 3 4 5 6
package com.tanpu.community.service;

import com.tanpu.common.util.JsonUtil;
import com.tanpu.community.api.beans.qo.ThemeAnalysDO;
import com.tanpu.community.api.beans.resp.PythonResponse;
import com.tanpu.community.dao.entity.community.ThemeEntity;
7
import com.tanpu.community.util.BizUtils;
8
import com.tanpu.community.util.ConvertUtil;
刘基明's avatar
刘基明 committed
9
import com.tanpu.community.util.TimeUtils;
10 11 12 13 14 15
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

刘基明's avatar
刘基明 committed
16
import javax.annotation.Resource;
刘基明's avatar
刘基明 committed
17
import java.time.LocalDateTime;
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RecommendService {

    @Value("${recommend.python.enable}")
    public String enablePython;


    @Value("${recommend.python.url}")
    public String pythonUrl;
    @Value("${recommend.ratio.hot}")
    public Integer hotRatio;
    @Value("${recommend.ratio.new}")
    public Integer newRatio;
    @Value("${recommend.ratio.python}")
    public Integer pythonRatio;


    @Autowired
    private RankService rankService;

    @Autowired
    private ThemeService themeService;

45

刘基明's avatar
刘基明 committed
46 47 48
    @Resource
    private RestTemplate restTemplate;

张辰's avatar
张辰 committed
49
    // 最新
50
    private List<ThemeAnalysDO> recentThemeList = new ArrayList<>();
张辰's avatar
张辰 committed
51
    // 推荐
52 53
    private Map<String, List<String>> recommondList = new HashMap<>();

刘基明's avatar
刘基明 committed
54
    public List<String> getRecommendThemes(Integer pageStart, Integer pageSize, String userId, List<String> excludeIds, LocalDateTime timeAfter) {
张辰's avatar
张辰 committed
55
        // 最热话题,筛掉用户发表的 & 最近看过的 & excludeIds
张辰's avatar
张辰 committed
56
        List<String> hotThemeIds = rankService.getHotestThemes().stream()
张辰's avatar
张辰 committed
57
                .filter(theme -> {
刘基明's avatar
刘基明 committed
58 59
                    // 暂时不过滤用户自己发的 !userId.equals(theme.getAuthorId());
                    return !excludeIds.contains(theme.getThemeId());
张辰's avatar
张辰 committed
60 61
                })
                .map(ThemeAnalysDO::getThemeId).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
62
        hotThemeIds = BizUtils.subList(hotThemeIds, 0, pageSize);
63

刘基明's avatar
刘基明 committed
64 65
        //最新话题,筛掉用户发表的 & 最近看过的 & excludeIds && 上次查询后发帖的
        long margin = TimeUtils.calMinuteTillNow(timeAfter);
66
        List<String> newThemeIds = getNewestThemes().stream()
张辰's avatar
张辰 committed
67
                .filter(theme -> {
刘基明's avatar
刘基明 committed
68
                    // 暂时不过滤用户自己发的 !userId.equals(theme.getAuthorId());
刘基明's avatar
刘基明 committed
69
                    return !excludeIds.contains(theme.getThemeId()) && theme.getMinutesTillNow() > margin;
张辰's avatar
张辰 committed
70 71
                })
                .map(ThemeAnalysDO::getThemeId).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
72
        newThemeIds = BizUtils.subList(newThemeIds, 0, pageSize);
73 74

        //推荐话题
张辰's avatar
张辰 committed
75 76 77 78 79 80 81 82
        List<String> recThemeIds = getPythonRecommendList(userId).stream()
                .filter(id -> {
                    return !excludeIds.contains(id);
                }).collect(Collectors.toList());
        recThemeIds = BizUtils.subList(recThemeIds, pageStart, pageSize);

        // merge
        return mergeRecommend(hotThemeIds, newThemeIds, recThemeIds);
83 84
    }

85 86
    // 获取最新话题
    public List<ThemeAnalysDO> getNewestThemes() {
87 88 89 90 91 92
        if (recentThemeList.size() == 0) {
            refreshNewestThemes();
        }
        return recentThemeList;
    }

93
    // 从数据库查询最新主题
94 95 96 97 98 99 100 101 102 103 104 105 106 107
    public void refreshNewestThemes() {
        List<ThemeEntity> themeEntities = themeService.queryLatestThemes(100);
        this.recentThemeList = ConvertUtil.themeEntityToAnalysDOs(themeEntities);
    }

    //查询python计算推荐列表
    public List<String> getPythonRecommendList(String userId) {
        if (recommondList.containsKey(userId)) {
            return recommondList.get(userId);
        } else {
            return refreshPythonRecommendList(userId);
        }
    }

108
    //HTTP查询python推荐主题 python返回最多50个
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    public List<String> refreshPythonRecommendList(String userId) {
        if (!"true".equals(enablePython)) {
            return Collections.emptyList();
        }
        HashMap<String, String> param = new HashMap<>();
        param.put("user_id", userId);
        try {
            String response = restTemplate.getForObject(pythonUrl, String.class, param);
            PythonResponse pythonResponse = JsonUtil.toBean(response, PythonResponse.class);
            recommondList.put(userId, pythonResponse.getAttributes());
            return pythonResponse.getAttributes();
        } catch (Exception e) {
            log.error("调用python失败");
            return Collections.emptyList();
        }


    }

刘基明's avatar
刘基明 committed
128 129
    // 合并,去重
    private List<String> mergeList(List<String> hotThemeIds, List<String> newThemeIds, List<String> recThemeIds, Set<String> set) {
刘基明's avatar
刘基明 committed
130
        ArrayList<String> result = new ArrayList<>();
刘基明's avatar
刘基明 committed
131
        // 3个集合的指针
刘基明's avatar
刘基明 committed
132 133 134 135 136 137 138 139
        Integer hotIdx = 0;
        Integer newIdx = 0;
        Integer recIdx = 0;
        while (hotThemeIds.size() > hotIdx || newThemeIds.size() > newIdx || recThemeIds.size() > recIdx) {
            int hotTimes = hotRatio;
            int newTimes = newRatio;
            int recTimes = pythonRatio;
            String id;
刘基明's avatar
刘基明 committed
140 141
            while (newTimes > 0 && newThemeIds.size() > newIdx) {
                id = newThemeIds.get(newIdx);
刘基明's avatar
刘基明 committed
142
                if (!set.contains(id)) {
刘基明's avatar
刘基明 committed
143 144 145 146
                    result.add(id);
                    set.add(id);
                }

刘基明's avatar
刘基明 committed
147 148
                newIdx++;
                newTimes--;
刘基明's avatar
刘基明 committed
149
            }
刘基明's avatar
刘基明 committed
150 151 152

            while (hotTimes > 0 && hotThemeIds.size() > hotIdx) {
                id = hotThemeIds.get(hotIdx);
刘基明's avatar
刘基明 committed
153
                if (!set.contains(id)) {
刘基明's avatar
刘基明 committed
154 155 156 157
                    result.add(id);
                    set.add(id);
                }

刘基明's avatar
刘基明 committed
158 159
                hotIdx++;
                hotTimes--;
刘基明's avatar
刘基明 committed
160
            }
刘基明's avatar
刘基明 committed
161

刘基明's avatar
刘基明 committed
162 163
            while (recTimes > 0 && recThemeIds.size() > recIdx) {
                id = recThemeIds.get(recIdx);
刘基明's avatar
刘基明 committed
164
                if (!set.contains(id)) {
刘基明's avatar
刘基明 committed
165 166 167 168
                    result.add(id);
                    set.add(id);
                }

刘基明's avatar
刘基明 committed
169 170 171 172 173 174 175 176
                recIdx++;
                recTimes--;
            }

        }
        return result;
    }

177 178 179 180 181
    // 按照 6,3,1的比例
    private List<String> mergeRecommend(List<String> hotIds, List<String> newestIds, List<String> recmdIds) {
        List<String> retList = new ArrayList<>();
        int round = 0;
        while (true) {
刘基明's avatar
刘基明 committed
182 183 184 185

            int newestStart = round * newRatio;
            int hotStart = round * hotRatio;
            int recmdStart = round * pythonRatio;
186 187
            if (hotStart >= hotIds.size() && newestStart >= newestIds.size() && recmdStart >= recmdIds.size()) {
                break;
188
            }
刘基明's avatar
刘基明 committed
189 190 191
            retList.addAll(BizUtils.subList(newestIds, newestStart, newRatio));
            retList.addAll(BizUtils.subList(hotIds, hotStart, hotRatio));
            retList.addAll(BizUtils.subList(recmdIds, recmdStart, pythonRatio));
192

193
            round++;
194
        }
刘基明's avatar
刘基明 committed
195
        return retList;
196 197 198 199
    }


}