blob: 6f6f9a4eaf6b80f53a459a571922f05518a32b6d [file] [log] [blame]
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);
// }
}