推荐算法 用户考核 头像上传

Change-Id: Iaac96768d5238142f5ed445f5cc64ccedd239d0f
diff --git a/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java b/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java
index 85145f4..9472aa0 100644
--- a/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java
+++ b/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java
@@ -9,13 +9,23 @@
 
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
-        // 帖子图片:D:/postuploads/  →  /images/**
-        registry.addResourceHandler("/images/**")
-                .addResourceLocations("file:D:/postuploads/");//路径改一下
+        // 帖子图片:D:/postuploads/  →  /images/** 求助帖
+        registry.addResourceHandler("/activityimgs/**")
+                .addResourceLocations("file:./activityimgs/");//路径改一下
+        // 帖子图片:D:/postuploads/  →  /images/** 求助帖
+        registry.addResourceHandler("/helppostimgs/**")
+                .addResourceLocations("file:./helppostimgs/");//路径改一下
 
         // ✅ 求助帖图片:D:/uploads/  →  /uploads/**
-        registry.addResourceHandler("/uploads/**")
-                .addResourceLocations("file:D:/uploads/");
+        // 映射到项目根目录下的postimgs目录,这是帖子的
+        registry.addResourceHandler("/postimgs/**")
+                .addResourceLocations("file:./postimgs/");
+
+        registry.addResourceHandler("/coverimgs/**")
+                .addResourceLocations("file:./coverimgs/");
+
+        registry.addResourceHandler("/userimgs/**")
+                .addResourceLocations("file:./userimgs/");
     }
 
 
diff --git a/src/main/java/com/pt5/pthouduan/controller/ExamController.java b/src/main/java/com/pt5/pthouduan/controller/ExamController.java
new file mode 100644
index 0000000..a5941be
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/controller/ExamController.java
@@ -0,0 +1,27 @@
+package com.pt5.pthouduan.controller;
+
+import com.pt5.pthouduan.service.ExamService;
+import com.pt5.pthouduan.service.ShopService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDate;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/exam")
+public class ExamController {
+    @Autowired
+    private ExamService examService;
+    @PostMapping("/MonthDownload")
+    public Map<String, Object> MonthDownload(@RequestParam LocalDate startDate, @RequestParam LocalDate endDate) {
+        return examService.MonthDownload(startDate, endDate);
+    }
+    @PostMapping("/QuarterUpload")
+    public Map<String, Object> QuarterUpload(@RequestParam LocalDate startDate, @RequestParam LocalDate endDate) {
+        return examService.QuarterUpload(startDate, endDate);
+    }
+}
diff --git a/src/main/java/com/pt5/pthouduan/controller/RecommendController.java b/src/main/java/com/pt5/pthouduan/controller/RecommendController.java
new file mode 100644
index 0000000..da981cb
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/controller/RecommendController.java
@@ -0,0 +1,26 @@
+package com.pt5.pthouduan.controller;
+
+import com.pt5.pthouduan.entity.Torrent;
+import com.pt5.pthouduan.entity.User;
+import com.pt5.pthouduan.service.InviteService;
+import com.pt5.pthouduan.service.RecommendService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/recommend")
+public class RecommendController {
+    @Autowired
+    private RecommendService recommendService;
+//    @GetMapping("/test")
+//    public List<Map<String, Object>> test() {
+//        return recommendService.test();
+//    }
+        @GetMapping("/list")
+    public List<Torrent> getRecommendList(@RequestParam Long userId) {
+        return recommendService.recommendForUser(userId);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/controller/UserController.java b/src/main/java/com/pt5/pthouduan/controller/UserController.java
index 4a23ea9..11f77ce 100644
--- a/src/main/java/com/pt5/pthouduan/controller/UserController.java
+++ b/src/main/java/com/pt5/pthouduan/controller/UserController.java
@@ -31,7 +31,11 @@
 public class UserController {
     @Autowired
     private UserService userService;
-    private String uploadDir="../var/www/avatars/";  // 配置文件上传目录,例如: /var/www/avatars/
+
+    @Value("${torrent.user-image-dir}")
+    private String uploadDir;
+
+    //private String uploadDir="../var/www/avatars/";  // 配置文件上传目录,例如: /var/www/avatars/
 
     private String accessPath="../avatars/";
 
@@ -113,68 +117,68 @@
         return userService.getuserid(username);
     }
 
-    @PostMapping("/uploadimage")
-    public ResponseEntity<Map<String, Object>> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
-        Map<String, Object> response = new HashMap<>();
+//    @PostMapping("/uploadimage")
+//    public ResponseEntity<Map<String, Object>> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
+//        Map<String, Object> response = new HashMap<>();
+//
+//        // 1. 验证文件是否为空
+//        if (file.isEmpty()) {
+//            response.put("success", false);
+//            response.put("message", "请选择要上传的文件");
+//            return ResponseEntity.badRequest().body(response);
+//        }
+//
+//        // 2. 验证文件类型
+//        String contentType = file.getContentType();
+//        if (!"image/jpeg".equals(contentType) &&
+//                !"image/png".equals(contentType) &&
+//                !"image/gif".equals(contentType)) {
+//            response.put("success", false);
+//            response.put("message", "只支持JPG/PNG/GIF格式的图片");
+//            return ResponseEntity.badRequest().body(response);
+//        }
+//
+//        // 3. 验证文件大小 (前端已验证,后端再次验证)
+//        if (file.getSize() > 10 * 1024 * 1024) { // 10MB
+//            response.put("success", false);
+//            response.put("message", "图片大小不能超过10MB");
+//            return ResponseEntity.badRequest().body(response);
+//        }
 
-        // 1. 验证文件是否为空
-        if (file.isEmpty()) {
-            response.put("success", false);
-            response.put("message", "请选择要上传的文件");
-            return ResponseEntity.badRequest().body(response);
-        }
-
-        // 2. 验证文件类型
-        String contentType = file.getContentType();
-        if (!"image/jpeg".equals(contentType) &&
-                !"image/png".equals(contentType) &&
-                !"image/gif".equals(contentType)) {
-            response.put("success", false);
-            response.put("message", "只支持JPG/PNG/GIF格式的图片");
-            return ResponseEntity.badRequest().body(response);
-        }
-
-        // 3. 验证文件大小 (前端已验证,后端再次验证)
-        if (file.getSize() > 10 * 1024 * 1024) { // 10MB
-            response.put("success", false);
-            response.put("message", "图片大小不能超过10MB");
-            return ResponseEntity.badRequest().body(response);
-        }
-
-        try {
-            // 4. 创建上传目录(如果不存在)
-            File dir = new File(uploadDir);
-            if (!dir.exists()) {
-                dir.mkdirs();
-            }
-            System.out.println(dir.getAbsolutePath());
-            // 5. 生成唯一文件名 (日期+UUID+后缀)
-            String originalFilename = file.getOriginalFilename();
-            String fileExt = originalFilename.substring(originalFilename.lastIndexOf("."));
-            String newFilename = new SimpleDateFormat("yyyyMMdd").format(new Date()) +
-                    "_" + UUID.randomUUID().toString().replace("-", "") +
-                    fileExt.toLowerCase();
-
-            // 6. 保存文件
-            Path path = Paths.get(uploadDir, newFilename);
-            Files.copy(file.getInputStream(), path);
-
-            // 7. 返回访问URL
-            String fileUrl = accessPath + newFilename;
-
-            response.put("success", true);
-            response.put("url", fileUrl);
-            response.put("message", "头像上传成功");
-
-            return ResponseEntity.ok(response);
-
-        } catch (IOException e) {
-            e.printStackTrace();
-            response.put("success", false);
-            response.put("message", "文件上传失败: " + e.getMessage());
-            return ResponseEntity.status(500).body(response);
-        }
-    }
+//        try {
+//            // 4. 创建上传目录(如果不存在)
+//            File dir = new File(uploadDir);
+//            if (!dir.exists()) {
+//                boolean created = dir.mkdirs();  // 递归创建目录
+//                if (!created) {
+//                    throw new IOException("无法创建目录:" + uploadDir);
+//                }
+//            }
+//            System.out.println(dir.getAbsolutePath());
+//            String filename = System.currentTimeMillis()+"_"+file.getOriginalFilename();
+//            Path userimagePath = Paths.get(uploadDir, filename);
+//
+//            // 6. 保存文件
+//            Files.createDirectories(userimagePath.getParent()); // 创建目录
+//            Files.copy(file.getInputStream(), userimagePath, StandardCopyOption.REPLACE_EXISTING);
+//
+//            // 7. 返回访问URL
+//            //String fileUrl = accessPath + newFilename;
+//            String fileUrl = "http://localhost:8080/" + uploadDir + filename;
+//
+//            response.put("success", true);
+//            response.put("url", fileUrl);
+//            response.put("message", "头像上传成功");
+//
+//            return ResponseEntity.ok(response);
+//
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//            response.put("success", false);
+//            response.put("message", "文件上传失败: " + e.getMessage());
+//            return ResponseEntity.status(500).body(response);
+//        }
+    //}
 
 
 }
diff --git a/src/main/java/com/pt5/pthouduan/entity/BehaviorType.java b/src/main/java/com/pt5/pthouduan/entity/BehaviorType.java
new file mode 100644
index 0000000..6e233d0
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/BehaviorType.java
@@ -0,0 +1,27 @@
+package com.pt5.pthouduan.entity;
+
+public enum BehaviorType {
+    VIEW(1, "浏览"),
+    DOWNLOAD(3, "下载"),
+    FAVORITE(5, "收藏"),
+    SEED(4, "做种"),
+    COMMENT(2, "评论"),
+    RATE(3, "评分"),
+    SHARE(2, "分享");
+
+    private final int weight;
+    private final String description;
+
+    BehaviorType(int weight, String description) {
+        this.weight = weight;
+        this.description = description;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/entity/UserBehavior.java b/src/main/java/com/pt5/pthouduan/entity/UserBehavior.java
new file mode 100644
index 0000000..192d3cd
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/UserBehavior.java
@@ -0,0 +1,94 @@
+package com.pt5.pthouduan.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 用户行为记录表
+ * </p>
+ *
+ * @author ljx
+ * @since 2025-06-04
+ */
+@TableName("user_behavior")
+public class UserBehavior implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId("behavior_id")
+    private Long behaviorId;    //行为id
+
+    private Long userId;        // 用户ID
+
+    private Long torrentId;     // 种子资源ID
+
+    private String behaviorType; // 行为类型:VIEW/DOWNLOAD/FAVORITE等
+
+    private Integer weight;     // 行为权重
+
+    private LocalDateTime timestamp; // 行为时间
+
+    public Long getBehaviorId() {
+        return behaviorId;
+    }
+
+    public void setBehaviorId(Long behaviorId) {
+        this.behaviorId = behaviorId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Long getTorrentId() {
+        return torrentId;
+    }
+
+    public void setTorrentId(Long torrentId) {
+        this.torrentId = torrentId;
+    }
+
+    public String getBehaviorType() {
+        return behaviorType;
+    }
+
+    public void setBehaviorType(String behaviorType) {
+        this.behaviorType = behaviorType;
+    }
+
+    public Integer getWeight() {
+        return weight;
+    }
+
+    public void setWeight(Integer weight) {
+        this.weight = weight;
+    }
+
+    public LocalDateTime getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(LocalDateTime timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public String toString() {
+        return "UserBehavior{" +
+                "behaviorId=" + behaviorId +
+                ", userId=" + userId +
+                ", torrentId=" + torrentId +
+                ", behaviorType='" + behaviorType + '\'' +
+                ", weight=" + weight +
+                ", timestamp=" + timestamp +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/mapper/RecommendMapper.java b/src/main/java/com/pt5/pthouduan/mapper/RecommendMapper.java
new file mode 100644
index 0000000..54f9e1d
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/mapper/RecommendMapper.java
@@ -0,0 +1,71 @@
+package com.pt5.pthouduan.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pt5.pthouduan.entity.UserBehavior;
+import org.apache.ibatis.annotations.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Map;
+
+
+@Repository
+public interface RecommendMapper extends BaseMapper<UserBehavior> {
+
+
+    @Insert("INSERT INTO user_behavior(user_id, torrent_id, behavior_type, weight, timestamp) " +
+            "VALUES(#{userId}, #{torrentId}, #{behaviorType}, #{weight}, #{timestamp})")
+    int insertUserBehavior(UserBehavior userBehavior);
+
+    @Select("SELECT COALESCE(SUM(weight), 0) FROM user_behavior WHERE user_id = #{userId}")
+    double sumWeightsByUserId(@Param("userId") Long userId);
+    /**
+     * 查找用户最喜欢的分类
+     */
+    @Select("SELECT torrent.categoryid as categoryId, SUM(ub.weight) as totalWeight " +
+            "FROM user_behavior ub " +
+            "JOIN torrent ON ub.torrent_id = torrent.torrentid " +
+            "WHERE ub.user_id = #{userId} " +
+            "GROUP BY torrent.categoryid " +
+            "ORDER BY totalWeight DESC ")
+    List<Map<String, Object>> findFavoriteCategories(@Param("userId") Long userId);
+
+    /**
+     * 统计用户行为总数
+     */
+    @Select("SELECT COUNT(*) FROM user_behavior WHERE user_id = #{userId}")
+    long countByUserId(@Param("userId") Long userId);
+
+    /**
+     * 获取用户最近的行为记录
+     */
+    @Select("SELECT * FROM user_behavior WHERE user_id = #{userId} ORDER BY timestamp DESC LIMIT #{limit}")
+    List<UserBehavior> findRecentBehaviors(@Param("userId") Long userId, @Param("limit") int limit);
+
+    /**
+     * 获取用户对特定资源的行为记录
+     */
+    @Select("SELECT * FROM user_behavior WHERE user_id = #{userId} AND torrent_id = #{torrentId}")
+    List<UserBehavior> findByUserAndTorrent(@Param("userId") Long userId, @Param("torrentId") Long torrentId);
+
+    /**
+     * 获取用户的高权重行为
+     */
+    @Select("SELECT * FROM user_behavior WHERE user_id = #{userId} AND weight >= #{minWeight}")
+    List<UserBehavior> findHighWeightBehaviors(@Param("userId") Long userId, @Param("minWeight") Integer minWeight);
+
+    /**
+     * 获取用户最近交互的资源ID
+     */
+    @Select("SELECT DISTINCT torrent_id FROM user_behavior " +
+            "WHERE user_id = #{userId} AND timestamp >= DATE_SUB(NOW(), INTERVAL #{days} DAY)")
+    List<Long> findRecentTorrentIds(@Param("userId") Long userId, @Param("days") int days);
+
+
+
+    /**
+     * 删除旧的行为记录
+     */
+    @Delete("DELETE FROM user_behavior WHERE timestamp < DATE_SUB(NOW(), INTERVAL #{days} DAY)")
+    int deleteOldBehaviorsForAllUsers(@Param("days") int days);
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/service/ExamService.java b/src/main/java/com/pt5/pthouduan/service/ExamService.java
new file mode 100644
index 0000000..93dbbcd
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/ExamService.java
@@ -0,0 +1,103 @@
+package com.pt5.pthouduan.service;
+
+import com.pt5.pthouduan.entity.Invites;
+import com.pt5.pthouduan.entity.User;
+import com.pt5.pthouduan.entity.UserTrafficStat;
+import com.pt5.pthouduan.mapper.InvitesMapper;
+import com.pt5.pthouduan.mapper.UserMapper;
+import com.pt5.pthouduan.mapper.UserTrafficMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class ExamService {
+    @Autowired
+    private UserTrafficMapper userTrafficMapper;
+    @Autowired
+    private UserMapper userMapper;
+    //所有用户月度下载量考核
+    public Map<String, Object> MonthDownload(LocalDate startDate,LocalDate endDate){
+        Map<String, Object> result = new HashMap<>();
+        List<Map<String, Object>> users = userMapper.selectAllUsersBasicInfo();
+        for (Map<String, Object> user : users) {
+            // 获取 gradeId,这里转为数字
+            Object gradeIdObj = user.get("grade_id");
+            int gradeId = (gradeIdObj instanceof Number) ? ((Number) gradeIdObj).intValue() : 0;
+            UserTrafficStat userTrafficStat=userTrafficMapper.getUserTrafficStats((String) user.get("passkey"),startDate,endDate);
+            System.out.println(gradeId+" "+userTrafficStat.getTotalUploaded()+"  "+userTrafficStat.getTotalDownloaded());
+            // 根据 gradeId 的值审核下载量
+            if (gradeId == 1) {
+                if(userTrafficStat.getTotalDownloaded()<1073741824){
+                    if(userTrafficStat.getTotalUploaded()< 1073741824L *50)
+                        failure((String) user.get("username"),gradeId);
+                }
+            } else if (gradeId == 2) {
+                if(userTrafficStat.getTotalDownloaded()< 1073741824L *3){
+                    if(userTrafficStat.getTotalUploaded()< 1073741824L *50)
+                        failure((String) user.get("username"),gradeId);
+                }
+            } else if (gradeId == 3) {
+                if(userTrafficStat.getTotalDownloaded()< 1073741824L *5){
+                    if(userTrafficStat.getTotalUploaded()< 1073741824L *50)
+                        failure((String) user.get("username"),gradeId);
+                }
+            }
+        }
+        result.put("success", true);
+        result.put("message", "用户月度考核完毕");
+        return result;
+    }
+    //考核失败
+    void failure(String username,int gradeId){
+        System.out.println("failure"+username+gradeId);
+        if(gradeId == 1){
+            userMapper.deleteByUsername(username);
+        }else{
+            userMapper.updateGrade(username,gradeId-1);
+        }
+    }
+    //所有用户季度上传量考核
+    public Map<String, Object> QuarterUpload(LocalDate startDate,LocalDate endDate){
+        Map<String, Object> result = new HashMap<>();
+        List<Map<String, Object>> users = userMapper.selectAllUsersBasicInfo();
+        for (Map<String, Object> user : users) {
+            // 获取 gradeId,这里转为数字
+            Object gradeIdObj = user.get("grade_id");
+            int gradeId = (gradeIdObj instanceof Number) ? ((Number) gradeIdObj).intValue() : 0;
+            UserTrafficStat userTrafficStat=userTrafficMapper.getUserTrafficStats((String) user.get("passkey"),startDate,endDate);
+            // 根据 gradeId 的值审核上传
+            if (gradeId == 1) {
+                if(userTrafficStat.getTotalUploaded()< 1073741824L *50){
+                    failure((String) user.get("username"),gradeId);
+                }
+            } else if (gradeId == 2) {
+                if(userTrafficStat.getTotalUploaded()< 1073741824L*60){
+                    failure((String) user.get("username"),gradeId);
+                }
+            } else if (gradeId == 3) {
+                System.out.println("here");
+                if(userTrafficStat.getTotalUploaded()< 1073741824L*70){
+                    System.out.println("failure");
+                    failure((String) user.get("username"),gradeId);
+                }
+            }else if (gradeId == 4) {
+                if(userTrafficStat.getTotalUploaded()< 1073741824L*80){
+                    failure((String) user.get("username"),gradeId);
+                }
+            }else if (gradeId == 5) {
+                if(userTrafficStat.getTotalUploaded()< 1073741824L*100){
+                    failure((String) user.get("username"),gradeId);
+                }
+            }
+        }
+        result.put("success", true);
+        result.put("message", "用户季度考核完毕");
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/service/RecommendService.java b/src/main/java/com/pt5/pthouduan/service/RecommendService.java
new file mode 100644
index 0000000..6f6f9a4
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/RecommendService.java
@@ -0,0 +1,170 @@
+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);
+//    }
+}