blob: 6f6f9a4eaf6b80f53a459a571922f05518a32b6d [file] [log] [blame]
21301050f5f827d2025-06-09 15:09:33 +08001package com.pt5.pthouduan.service;
2
3import com.pt5.pthouduan.entity.Torrent;
4import com.pt5.pthouduan.entity.UserBehavior;
5import com.pt5.pthouduan.mapper.RecommendMapper;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.stereotype.Service;
8
9import java.time.LocalDateTime;
10import java.time.temporal.ChronoUnit;
11import java.util.*;
12import java.util.stream.Collectors;
13
14
15@Service
16public class RecommendService {
17 @Autowired
18 private TorrentService torrentService;
19
20 @Autowired
21 private RecommendMapper recommendMapper;
22 //收集用户行为数据
23 public void recordUserBehavior(Long userId, Long torrentId, String behaviorType) {
24 UserBehavior behavior = new UserBehavior();
25 behavior.setUserId(userId);
26 behavior.setTorrentId(torrentId);
27 behavior.setBehaviorType(behaviorType);
28 behavior.setWeight(getBehaviorWeight(behaviorType));
29 behavior.setTimestamp(LocalDateTime.now());
30
31 recommendMapper.insertUserBehavior(behavior);
32 }
33 //对用户行为数据进行定期清洗 维护内存空间
34 private void cleanOldBehaviors() {
35 recommendMapper.deleteOldBehaviorsForAllUsers(30);
36 }
37
38 //设定不同用户行为的偏好权重(注意这里权重如果修改 那么BehaviorType同样需要修改 它在entity中)
39 private int getBehaviorWeight(String behaviorType) {
40 return switch (behaviorType) {
41 case "DOWNLOAD" -> 3;
42 case "FAVORITE" -> 5;
43 case "SEED" -> 4;
44 case "VIEW" -> 1;
45 case "COMMENT" -> 2;
46 default -> 1;
47 };
48 }
49
50
51
52 public List<Torrent> recommendForUser(Long userId) {//limit为资源的最大数量限制
53 // 检查用户是否有足够的行为数据
54 double totalWeight=recommendMapper.sumWeightsByUserId(userId);
55
56 if (totalWeight>50) {//行为数据足够
57 // 70% 偏好资源,15% 热门资源,15% 最新资源
58 return getPersonalizedRecommendation(userId);
59 } else {//行为数据不足
60 // 50% 热门资源,50% 最新资源
61 return getDefaultRecommendation();
62 }
63 }
64
65 //偏好资源推荐
66 private List<Torrent> getPersonalizedRecommendation(Long userId) {
67
68 // 获取偏好资源
69
70 List<Map<String, Object>> categories = recommendMapper.findFavoriteCategories(userId);//获取喜爱的类型及其权重
71 categories = categories.stream()
72 .sorted((a, b) -> Integer.compare(
73 ((Number)b.get("totalWeight")).intValue(),
74 ((Number)a.get("totalWeight")).intValue()
75 ))
76 .limit(5) // 只取前5种偏好类型
77 .toList();
78 List<List<Torrent>> allTorrents = categories.stream()
79 .map(category -> torrentService.getTorrentsByCategory((Integer) category.get("categoryId")))
80 .toList();
81 // 3. 均匀混合算法
82 List<Torrent> combinedList = new ArrayList<>();
83 if (!allTorrents.isEmpty()) {
84 int maxSize = allTorrents.stream()
85 .mapToInt(List::size)
86 .max()
87 .orElse(0);
88
89 // 按权重计算每种类型每次应该添加的元素数量
90 int[] weights = categories.stream()
91 .mapToInt(c -> ((Number)c.get("totalWeight")).intValue()) // 安全转换
92 .toArray();
93 int totalWeight = Arrays.stream(weights).sum();
94
95 // 计算每种类型在每轮中应该添加的比例
96 double[] ratios = Arrays.stream(weights)
97 .mapToDouble(w -> (double)w / totalWeight)
98 .toArray();
99
100 // 均匀混合实现
101 for (int i = 0; i < maxSize; i++) {
102 for (int j = 0; j < allTorrents.size(); j++) {
103 List<Torrent> current = allTorrents.get(j);
104 if (i < current.size()) {
105 // 根据权重比例决定是否添加当前元素
106 if (Math.random() < ratios[j]) {
107 combinedList.add(current.get(i));
108 }
109 }
110 }
111 }
112 }
113
114 // 常规推荐
115 List<Torrent> regular=getDefaultRecommendation();
116 //合并结果 把个性化推荐和普通推荐相结合7:3
117 List<Torrent> finalList = new ArrayList<>();
118 int combinedSize = combinedList.size();
119 int regularSize = regular.size();
120 int actualCombined = combinedSize;
121 int actualRegular =regularSize;
122 Iterator<Torrent> combinedIter = combinedList.iterator();
123 Iterator<Torrent> regularIter = regular.iterator();
124 while (actualCombined > 0 || actualRegular > 0) {
125 double ratio = 0.9;
126
127 if (Math.random() < ratio && actualCombined > 0) {
128 if (combinedIter.hasNext()) {
129 finalList.add(combinedIter.next());
130 actualCombined--;
131 }
132 } else if (actualRegular > 0) {
133 if (regularIter.hasNext()) {
134 finalList.add(regularIter.next());
135 actualRegular--;
136 }
137 }
138 }
139 while (regularIter.hasNext()) {
140 finalList.add(regularIter.next());
141 }
142 Set<Integer> seenIds = new HashSet<>();
143 List<Torrent> distinctList = finalList.stream()
144 .filter(torrent -> seenIds.add(torrent.getTorrentid().intValue())) // 首次出现时返回true
145 .collect(Collectors.toList());
146 return distinctList;
147 }
148 //无偏好推荐
149 private List<Torrent> getDefaultRecommendation() {
150 List<Torrent> torrents=torrentService.getAllTorrents();
151 torrents= torrents.stream()
152 .sorted((t1, t2) -> {
153 // 时间衰减因子(新种子加分)
154 long daysOld1 = ChronoUnit.DAYS.between(t1.getUploadTime(), LocalDateTime.now());
155 long daysOld2 = ChronoUnit.DAYS.between(t2.getUploadTime(), LocalDateTime.now());
156
157 // 综合分数 = downloadCount + (1 / (daysOld + 1)) * 系数
158 double score1 = t1.getDownloadCount() + 10.0 / (daysOld1 + 1);
159 double score2 = t2.getDownloadCount() + 10.0 / (daysOld2 + 1);
160
161 return Double.compare(score2, score1);
162 })
163 .toList();
164 return torrents;
165 }
166
167// public List<Map<String, Object>> test(){
168// return recommendMapper.findFavoriteCategories(1L);
169// }
170}