| package com.pt5.pthouduan.service; |
| |
| import com.pt5.pthouduan.entity.Torrent; |
| import com.pt5.pthouduan.entity.UserBehavior; |
| import com.pt5.pthouduan.mapper.RecommendMapper; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.stereotype.Service; |
| |
| import java.time.LocalDateTime; |
| import java.time.temporal.ChronoUnit; |
| import java.util.*; |
| import java.util.stream.Collectors; |
| |
| |
| @Service |
| public class RecommendService { |
| @Autowired |
| private TorrentService torrentService; |
| |
| @Autowired |
| private RecommendMapper recommendMapper; |
| //收集用户行为数据 |
| public void recordUserBehavior(Long userId, Long torrentId, String behaviorType) { |
| UserBehavior behavior = new UserBehavior(); |
| behavior.setUserId(userId); |
| behavior.setTorrentId(torrentId); |
| behavior.setBehaviorType(behaviorType); |
| behavior.setWeight(getBehaviorWeight(behaviorType)); |
| behavior.setTimestamp(LocalDateTime.now()); |
| |
| recommendMapper.insertUserBehavior(behavior); |
| } |
| //对用户行为数据进行定期清洗 维护内存空间 |
| private void cleanOldBehaviors() { |
| recommendMapper.deleteOldBehaviorsForAllUsers(30); |
| } |
| |
| //设定不同用户行为的偏好权重(注意这里权重如果修改 那么BehaviorType同样需要修改 它在entity中) |
| private int getBehaviorWeight(String behaviorType) { |
| return switch (behaviorType) { |
| case "DOWNLOAD" -> 3; |
| case "FAVORITE" -> 5; |
| case "SEED" -> 4; |
| case "VIEW" -> 1; |
| case "COMMENT" -> 2; |
| default -> 1; |
| }; |
| } |
| |
| |
| |
| public List<Torrent> recommendForUser(Long userId) {//limit为资源的最大数量限制 |
| // 检查用户是否有足够的行为数据 |
| double totalWeight=recommendMapper.sumWeightsByUserId(userId); |
| |
| if (totalWeight>50) {//行为数据足够 |
| // 70% 偏好资源,15% 热门资源,15% 最新资源 |
| return getPersonalizedRecommendation(userId); |
| } else {//行为数据不足 |
| // 50% 热门资源,50% 最新资源 |
| return getDefaultRecommendation(); |
| } |
| } |
| |
| //偏好资源推荐 |
| private List<Torrent> getPersonalizedRecommendation(Long userId) { |
| |
| // 获取偏好资源 |
| |
| List<Map<String, Object>> categories = recommendMapper.findFavoriteCategories(userId);//获取喜爱的类型及其权重 |
| categories = categories.stream() |
| .sorted((a, b) -> Integer.compare( |
| ((Number)b.get("totalWeight")).intValue(), |
| ((Number)a.get("totalWeight")).intValue() |
| )) |
| .limit(5) // 只取前5种偏好类型 |
| .toList(); |
| List<List<Torrent>> allTorrents = categories.stream() |
| .map(category -> torrentService.getTorrentsByCategory((Integer) category.get("categoryId"))) |
| .toList(); |
| // 3. 均匀混合算法 |
| List<Torrent> combinedList = new ArrayList<>(); |
| if (!allTorrents.isEmpty()) { |
| int maxSize = allTorrents.stream() |
| .mapToInt(List::size) |
| .max() |
| .orElse(0); |
| |
| // 按权重计算每种类型每次应该添加的元素数量 |
| int[] weights = categories.stream() |
| .mapToInt(c -> ((Number)c.get("totalWeight")).intValue()) // 安全转换 |
| .toArray(); |
| int totalWeight = Arrays.stream(weights).sum(); |
| |
| // 计算每种类型在每轮中应该添加的比例 |
| double[] ratios = Arrays.stream(weights) |
| .mapToDouble(w -> (double)w / totalWeight) |
| .toArray(); |
| |
| // 均匀混合实现 |
| for (int i = 0; i < maxSize; i++) { |
| for (int j = 0; j < allTorrents.size(); j++) { |
| List<Torrent> current = allTorrents.get(j); |
| if (i < current.size()) { |
| // 根据权重比例决定是否添加当前元素 |
| if (Math.random() < ratios[j]) { |
| combinedList.add(current.get(i)); |
| } |
| } |
| } |
| } |
| } |
| |
| // 常规推荐 |
| List<Torrent> regular=getDefaultRecommendation(); |
| //合并结果 把个性化推荐和普通推荐相结合7:3 |
| List<Torrent> finalList = new ArrayList<>(); |
| int combinedSize = combinedList.size(); |
| int regularSize = regular.size(); |
| int actualCombined = combinedSize; |
| int actualRegular =regularSize; |
| Iterator<Torrent> combinedIter = combinedList.iterator(); |
| Iterator<Torrent> regularIter = regular.iterator(); |
| while (actualCombined > 0 || actualRegular > 0) { |
| double ratio = 0.9; |
| |
| if (Math.random() < ratio && actualCombined > 0) { |
| if (combinedIter.hasNext()) { |
| finalList.add(combinedIter.next()); |
| actualCombined--; |
| } |
| } else if (actualRegular > 0) { |
| if (regularIter.hasNext()) { |
| finalList.add(regularIter.next()); |
| actualRegular--; |
| } |
| } |
| } |
| while (regularIter.hasNext()) { |
| finalList.add(regularIter.next()); |
| } |
| Set<Integer> seenIds = new HashSet<>(); |
| List<Torrent> distinctList = finalList.stream() |
| .filter(torrent -> seenIds.add(torrent.getTorrentid().intValue())) // 首次出现时返回true |
| .collect(Collectors.toList()); |
| return distinctList; |
| } |
| //无偏好推荐 |
| private List<Torrent> getDefaultRecommendation() { |
| List<Torrent> torrents=torrentService.getAllTorrents(); |
| torrents= torrents.stream() |
| .sorted((t1, t2) -> { |
| // 时间衰减因子(新种子加分) |
| long daysOld1 = ChronoUnit.DAYS.between(t1.getUploadTime(), LocalDateTime.now()); |
| long daysOld2 = ChronoUnit.DAYS.between(t2.getUploadTime(), LocalDateTime.now()); |
| |
| // 综合分数 = downloadCount + (1 / (daysOld + 1)) * 系数 |
| double score1 = t1.getDownloadCount() + 10.0 / (daysOld1 + 1); |
| double score2 = t2.getDownloadCount() + 10.0 / (daysOld2 + 1); |
| |
| return Double.compare(score2, score1); |
| }) |
| .toList(); |
| return torrents; |
| } |
| |
| // public List<Map<String, Object>> test(){ |
| // return recommendMapper.findFavoriteCategories(1L); |
| // } |
| } |