feat: 支持 Torrent 解析与数据持久化(v1/v2/Hybrid)

- 实现对 Torrent v1、v2 以及 Hybrid 模式的解析
- 完成 Torrent 元信息的数据库持久化逻辑
- 支持 announce 节点、文件信息的提取与存储
- 构建 BtTorrent 表结构与相关插入逻辑
- 预留 OSS 上传接口,明日补充实际上传实现
Change-Id: I4438da0ab364bc8e0d299e1d57474c190584052a
diff --git a/react-ui/src/pages/Torrent/index.tsx b/react-ui/src/pages/Torrent/index.tsx
index 6990da7..bb058ae 100644
--- a/react-ui/src/pages/Torrent/index.tsx
+++ b/react-ui/src/pages/Torrent/index.tsx
@@ -1,5 +1,5 @@
 import React, { useRef, useState } from 'react';
-import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
+import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined } from '@ant-design/icons';
 import {
   Button,
   Modal,
@@ -11,14 +11,10 @@
   DatePicker,
   Card,
   Layout,
+  Upload,
+  UploadProps,
 } from 'antd';
-import {
-  ProTable,
-  ActionType,
-  ProColumns,
-  ProDescriptions,
-  ProDescriptionsItemProps,
-} from '@ant-design/pro-components';
+import { ProTable, ActionType, ProColumns, ProDescriptions, ProDescriptionsItemProps } from '@ant-design/pro-components';
 import type { BtTorrent } from './data';
 import {
   listBtTorrent,
@@ -26,6 +22,7 @@
   addBtTorrent,
   updateBtTorrent,
   removeBtTorrent,
+  uploadTorrent, // Function to handle torrent upload
 } from './service';
 
 const { Content } = Layout;
@@ -36,7 +33,10 @@
   const [modalVisible, setModalVisible] = useState(false);
   const [drawerVisible, setDrawerVisible] = useState(false);
   const [current, setCurrent] = useState<Partial<BtTorrent>>({});
+  const [uploadModalVisible, setUploadModalVisible] = useState(false); // State for upload modal
+  const [uploadFile, setUploadFile] = useState<File | null>(null); // State to store selected file
 
+  // Columns for the ProTable (the table displaying torrents)
   const columns: ProColumns<BtTorrent>[] = [
     {
       title: '种子ID',
@@ -89,18 +89,47 @@
     },
   ];
 
+  // Handle the submit for adding or updating a torrent
   const handleSubmit = async () => {
     const values = await form.validateFields();
-    if (current?.torrentId) {
-      await updateBtTorrent({ ...current, ...values });
-      message.success('更新成功');
-    } else {
-      await addBtTorrent(values as BtTorrent);
-      message.success('新增成功');
+    try {
+      if (current?.torrentId) {
+        await updateBtTorrent({ ...current, ...values });
+        message.success('更新成功');
+      } else {
+        await addBtTorrent(values as BtTorrent);
+        message.success('新增成功');
+      }
+      setModalVisible(false);
+      form.resetFields();
+      actionRef.current?.reload();
+    } catch (error) {
+      message.error('操作失败');
     }
-    setModalVisible(false);
-    form.resetFields();
-    actionRef.current?.reload();
+  };
+
+  // Handle file upload
+  const handleFileUpload = async (file: File) => {
+    try {
+      if (!file) {
+        throw new Error('请选择一个文件');
+      }
+
+      // Call the uploadTorrent function to upload the file
+      await uploadTorrent(file);
+
+      // Show a success message
+      message.success('文件上传成功');
+
+      // Close the upload modal
+      setUploadModalVisible(false);
+
+      // Optionally reload the table or perform other actions (e.g., refresh list)
+      actionRef.current?.reload();
+
+    } catch (error) {
+      message.error(error.message || '文件上传失败');
+    }
   };
 
   return (
@@ -124,6 +153,14 @@
             >
               新增
             </Button>,
+            <Button
+              key="upload"
+              type="primary"
+              icon={<UploadOutlined />}
+              onClick={() => setUploadModalVisible(true)} // Show the upload modal
+            >
+              上传种子文件
+            </Button>
           ]}
           request={async (params) => {
             const res = await listBtTorrent(params);
@@ -162,6 +199,26 @@
           </Form>
         </Modal>
 
+        {/* 上传种子文件的Modal */}
+        <Modal
+          title="上传种子文件"
+          visible={uploadModalVisible}
+          onCancel={() => setUploadModalVisible(false)}
+          footer={null}
+        >
+          <Upload
+            customRequest={({ file, onSuccess, onError }) => {
+              setUploadFile(file);
+              handleFileUpload(file);
+              onSuccess?.();
+            }}
+            showUploadList={false}
+            accept=".torrent"
+          >
+            <Button icon={<UploadOutlined />}>点击上传 .torrent 文件</Button>
+          </Upload>
+        </Modal>
+
         {/* 详情抽屉 */}
         <Drawer
           width={500}