推荐系统完成

Change-Id: I244590be01b1b4f37664a0e7f3103827e607ffbe
diff --git a/react-ui/src/pages/Torrent/service.ts b/react-ui/src/pages/Torrent/service.ts
index e526151..35d2312 100644
--- a/react-ui/src/pages/Torrent/service.ts
+++ b/react-ui/src/pages/Torrent/service.ts
@@ -19,10 +19,9 @@
 // ================================
 
 /** 查询种子列表 */
-export async function listBtTorrent(params?: Partial<BtTorrent>) {
-  const queryString = params ? `?${new URLSearchParams(params as any)}` : '';
-  return request(`/api/system/torrent/list${queryString}`, {
-    method: 'get',
+export async function listBtTorrent(userId: string) {
+  return request(`/api/system/torrent/recommend`, {
+    method: 'post',
   });
 }
 
diff --git a/recommend/download_model.py b/recommend/download_model.py
index 1bb0c50..b9555dc 100644
--- a/recommend/download_model.py
+++ b/recommend/download_model.py
@@ -1,6 +1,6 @@
 import os
 import urllib.request
-from recommend import train_and_save_itemcf
+
 MODEL_URL = "https://dl.fbaipublicfiles.com/fasttext/vectors-crawl/cc.zh.300.bin.gz"
 MODEL_DIR = "./models"
 MODEL_PATH = os.path.join(MODEL_DIR, "cc.zh.300.bin")
@@ -28,5 +28,4 @@
     print("✅ 模型下载并解压完成!")
 
 if __name__ == "__main__":
-    train_and_save_itemcf()
     download_model()
diff --git a/recommend/recommend.py b/recommend/recommend.py
index 25032a0..b216d52 100644
--- a/recommend/recommend.py
+++ b/recommend/recommend.py
@@ -15,7 +15,7 @@
 engine = create_engine("mysql+pymysql://sy:sy_password@49.233.215.144:3306/pt_station")
 
 # === ✅ 加载 fastText 模型 ===
-fasttext_model_path = 'E:\\course\\pt\\recommend\\models\\cc.zh.300.bin'
+fasttext_model_path = 'models\\cc.zh.300.bin'
 if not os.path.exists(fasttext_model_path):
     raise FileNotFoundError("fastText 模型文件不存在,请检查路径。")
 print("加载 fastText 模型中...")
@@ -25,7 +25,7 @@
 # === ✅ 用户标签行为矩阵构建 ===
 def get_user_tag_matrix():
     df = pd.read_sql("SELECT user_id, tag, score FROM user_tag_scores", engine)
-    print(df)
+    #print(df)
     df['user_id'] = df['user_id'].astype(str)
     user_map = {u: i for i, u in enumerate(df['user_id'].unique())}
     tag_map = {t: i for i, t in enumerate(df['tag'].unique())}
@@ -39,20 +39,27 @@
 def semantic_recommend(user_id, topn=5):
     print(f"正在为用户 {user_id} 生成推荐...")
 
-    # 读取数据库中的用户标签数据
+    # 读取数据
     df = pd.read_sql("SELECT user_id, tag, score FROM user_tag_scores", engine)
-    print(f"总记录数: {len(df)}")
-    print(f"数据示例:\n{df.head()}")
-    print(df.dtypes)
-    user_id = str(user_id)  # 确保匹配
 
-    # 获取该用户的所有标签(按分数从高到低排序)
+    # 统一类型转换
+    df['user_id'] = df['user_id'].astype(str)  # 确保整个列转为字符串
+    user_id = str(user_id)  # 要查询的ID也转为字符串
+
+    # 现在查询应该正常工作了
     user_tags = df[df['user_id'] == user_id].sort_values(by="score", ascending=False)['tag'].tolist()
     print(f"用户 {user_id} 的标签(按分数排序): {user_tags}")
 
     if not user_tags:
         print(f"用户 {user_id} 没有标签记录,返回空推荐结果。")
         return []
+    else:
+        user_tags = user_tags[:3]
+        print(f"用户 {user_id} 的 Top 3 标签: {user_tags}")
+
+    if not user_tags:
+        print(f"用户 {user_id} 没有标签记录,返回空推荐结果。")
+        return []
 
     # 截取前 3 个标签作为“兴趣标签”
     user_tags = user_tags[:3]
@@ -85,8 +92,8 @@
     # 排序并返回 topN 标签
     sorted_tags = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:topn]
     print(f"\n最终推荐标签(前 {topn}):")
-    for tag, score in sorted_tags:
-        print(f"{tag}: {score:.4f}")
+    #for tag, score in sorted_tags:
+      #  print(f"{tag}: {score:.4f}")
 
     return [tag for tag, _ in sorted_tags]
 
@@ -117,8 +124,6 @@
         print(f"⚠️ 用户 {user_id} 没有任何标签评分记录。")
         return []
 
-    print(f"用户 {user_id} 的标签评分:\n{user_tags}")
-
     scores = {}
     for tag, val in user_tags.items():
         if tag not in sim_df:
@@ -212,28 +217,32 @@
 def get_torrent_ids_by_tags(tags, limit_per_tag=10):
     if not tags:
         tags = []
+    print(f"传递给 get_torrent_ids_by_tags 的标签: {tags}")
 
     recommended_ids = set()
     with engine.connect() as conn:
         for tag in tags:
             query = text("""
                 SELECT torrent_id
-                FROM bt_torrent_tags 
-                WHERE tag = :tag 
+                FROM bt_torrent_tags
+                WHERE tag = :tag
                 LIMIT :limit
             """)
             result = conn.execute(query, {"tag": tag, "limit": limit_per_tag})
+            print(f"标签 '{tag}' 的推荐结果:")
             for row in result:
+                print(row[0])  # 打印每个torrent_id
                 recommended_ids.add(row[0])
 
         # 获取数据库中所有 torrent_id
-        all_query = text("SELECT DISTINCT torrent_id FROM bt_torrent_tags")
+        all_query = text("SELECT DISTINCT torrent_id FROM bt_torrent")
         all_result = conn.execute(all_query)
         all_ids = set(row[0] for row in all_result)
+        print("数据库中所有torrent_id:", all_ids)
 
     # 剩下的(非推荐)种子 ID
     remaining_ids = all_ids - recommended_ids
-
+    print(remaining_ids)
     # 随机打乱推荐和剩下的 ID
     recommended_list = list(recommended_ids)
     remaining_list = list(remaining_ids)
diff --git a/recommend/requirements.txt b/recommend/requirements.txt
index 2efe47a..b609f83 100644
--- a/recommend/requirements.txt
+++ b/recommend/requirements.txt
@@ -3,7 +3,7 @@
 huggingface_hub==0.31.2
 jieba==0.42.1
 mysql_connector_repackaged==0.3.1
-numpy==2.2.6
+numpy==1.26.4
 pandas==2.2.3
 scikit_learn==1.6.1
 scikit_surprise==1.1.4
@@ -16,3 +16,4 @@
 torch==2.7.0
 transformers==4.51.3
 waitress==3.0.2
+pymysql
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
index 15dc9f3..559547b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
@@ -6,6 +6,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.*;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.ruoyi.common.utils.http.HttpUtils;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -61,6 +62,33 @@
     @Autowired
     private IBtTorrentAnnounceService btTorrentAnnounceService;
     private static final String RECOMMEND_API = "http://127.0.0.1:5000/recommend_torrents";
+    private String sendJsonPost(String url, String jsonBody) throws IOException {
+        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+        connection.setRequestMethod("POST");
+        connection.setDoOutput(true);
+        connection.setRequestProperty("Content-Type", "application/json");
+        connection.setRequestProperty("Accept", "application/json");
+
+        try (OutputStream os = connection.getOutputStream()) {
+            os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
+        }
+
+        try (InputStream is = connection.getInputStream()) {
+            return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+        }
+    }
+    public static class TorrentRecommendationRequest {
+        @JsonProperty("torrent_ids")
+        private List<Long> ids;
+
+        public List<Long> getIds() {
+            return ids;
+        }
+
+        public void setIds(List<Long> ids) {
+            this.ids = ids;
+        }
+    }
 
     private String torrentPath= "torrents";
 
@@ -92,9 +120,6 @@
                 }
                 TorrentFileUtil.uploadFile(file,torrentPath+"/"+SecurityUtils.getUserId());
 
-
-
-
                 // Assuming the Flask server responds with JSON, parse the response
                 String responseBody = new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
                 parseTorrentData(responseBody);
@@ -155,7 +180,6 @@
                 btTorrentFileService.insertBtTorrentFile(btFile);
             }
             );
-
             // Convert btTorrentAnnounceNode to List<BtTorrentAnnounce>
             List<BtTorrentAnnounce> btTorrentAnnounceList = new ArrayList<>();
             if (btTorrentAnnounceNode.isArray()) {
@@ -170,18 +194,9 @@
                         btTorrentAnnounceService.insertBtTorrentAnnounce(btTorrentAnnounce);
                     }
             );
-
-
-
-
-
-
-
-
         } catch (Exception e) {
             e.printStackTrace();
             // Handle the error (e.g., return null or an error message)
-
         }
     }
 
@@ -227,22 +242,41 @@
      */
     @PreAuthorize("@ss.hasPermi('system:torrent:list')")
     @PostMapping("/recommend")
-    public TableDataInfo recommendTorrents(@RequestParam("user_id") String userId) {
+    public TableDataInfo recommendTorrents() {
+        // 假设当前用户的 user_id 是通过 SecurityContext 或 session 中获取的
+        String userId = String.valueOf(getUserId());  // 获取当前用户的 user_id
+
+        if (userId == null || userId.isEmpty()) {
+            TableDataInfo error = new TableDataInfo();
+            error.setCode(400);
+            error.setMsg("用户ID无效或未登录");
+            return error;
+        }
+
+        System.out.println("当前用户ID: " + userId);  // 打印调试信息
+
         try {
-            // 1. 构造 JSON 请求体
+            // 1. 构造 JSON 请求体,包含用户ID
             String jsonRequest = "{\"user_id\": \"" + userId + "\"}";
 
-            // 2. 向 Flask 服务发 POST 请求,返回 JSON 数组
-            String jsonResponse = HttpUtils.sendPost(RECOMMEND_API, jsonRequest);
+            // 2. 向 Flask 服务发 POST 请求,返回 JSON 响应
+            String jsonResponse = sendJsonPost(RECOMMEND_API, jsonRequest);
 
-            // 3. 使用 Jackson 解析 JSON 数组为 List<Long>
+            // 3. 使用 Jackson 解析 JSON 响应为 TorrentRecommendationRequest 对象
             ObjectMapper mapper = new ObjectMapper();
-            List<Long> idList = mapper.readValue(jsonResponse, new TypeReference<List<Long>>() {});
+            TorrentRecommendationRequest recommendationRequest = mapper.readValue(jsonResponse, TorrentRecommendationRequest.class);
 
-            // 4. 根据 ID 查询完整种子信息,并保持推荐顺序
+            // 4. 提取 ids 字段
+            List<Long> idList = recommendationRequest.getIds();
+
+            if (idList == null || idList.isEmpty()) {
+                throw new RuntimeException("推荐的种子ID列表为空");
+            }
+
+            // 5. 根据种子ID查询完整的种子信息,保持推荐顺序
             List<BtTorrent> resultList = btTorrentService.selectBtTorrentsByIdsOrdered(idList);
 
-            // 5. 封装成 TableDataInfo 返回
+            // 6. 封装查询结果并返回
             TableDataInfo rsp = new TableDataInfo();
             rsp.setCode(200);
             rsp.setMsg("推荐成功");
@@ -251,7 +285,9 @@
             return rsp;
 
         } catch (Exception e) {
-            e.printStackTrace();
+            e.printStackTrace();  // 打印异常信息
+
+            // 如果出错,返回错误信息
             TableDataInfo error = new TableDataInfo();
             error.setCode(500);
             error.setMsg("推荐失败:" + e.getMessage());
@@ -259,6 +295,7 @@
         }
     }
 
+
     /**
      * 导出种子主列表
      */
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentMapper.java
index 529043c..05fc282 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentMapper.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentMapper.java
@@ -68,5 +68,5 @@
      * @param ids 种子ID列表
      * @return 种子列表
      */
-    List<BtTorrent> selectBtTorrentsByIdsOrdered(@Param("list") List<Long> ids);
+    public List<BtTorrent> selectBtTorrentsByIdsOrdered(@Param("list") List<Long> ids);
 }
diff --git a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
index 4f5cf3f..097d7b8 100644
--- a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
+++ b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
@@ -45,14 +45,14 @@
         where torrent_id = #{torrentId}
     </select>
 
-    <select id="selectBtTorrentsByIdsOrdered" resultType="BtTorrent">
+    <select id="selectBtTorrentsByIdsOrdered" resultMap="BtTorrentResult">
         SELECT * FROM bt_torrent
-        WHERE id IN
-        <foreach collection="list" item="id" open="(" separator="," close=")">
+        WHERE torrent_id IN  <!-- 修改这里 -->
+        <foreach item="id" collection="list" open="(" separator="," close=")">
             #{id}
         </foreach>
-        ORDER BY FIELD(id
-        <foreach collection="list" item="id" separator=",">
+        ORDER BY FIELD(torrent_id,  <!-- 修改这里 -->
+        <foreach item="id" collection="list" separator=",">
             #{id}
         </foreach>
         )