feat: 完成 Tracker 项目与 Torrent 种子模块的前后端接口与页面开发

- 实现 Tracker 项目、任务、任务日志、项目用户关联等模块的接口封装与 ProTable 页面
- 实现 Torrent 种子主表、文件列表、Tracker 服务器、标签模块的前后端接口封装
- 支持新增、编辑、删除、详情查看等完整 CRUD 功能
- 页面基于 Ant Design Pro,支持分页、筛选、Drawer + Modal 表单展示

Change-Id: If8ead64a0bf6c177545f1c3c348ee09cad221a85
diff --git a/gen-Change-Id.ps1 b/gen-Change-Id.ps1
new file mode 100644
index 0000000..74caf05
--- /dev/null
+++ b/gen-Change-Id.ps1
@@ -0,0 +1,6 @@
+$bytes = New-Object Byte[] 20
+$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new()
+$rng.GetBytes($bytes)
+$changeId = "I" + [System.BitConverter]::ToString($bytes).Replace("-", "").ToLower()
+$formatted = "Change-Id: $($changeId.Insert(5,'').Insert(10,'').Insert(15,'').Insert(20,'').Insert(25,'').Insert(30,'').Insert(35,'').Insert(40,''))"
+$formatted
\ No newline at end of file
diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts
index ffad02f..919ae3b 100644
--- a/react-ui/config/routes.ts
+++ b/react-ui/config/routes.ts
@@ -73,6 +73,7 @@
       },
     ]
   },
+
   {
     name: 'tool',
     path: '/tool',
diff --git a/react-ui/package.json b/react-ui/package.json
index 4763d98..7c9ffe2 100644
--- a/react-ui/package.json
+++ b/react-ui/package.json
@@ -90,6 +90,7 @@
     "@types/react": "^18.3.0",
     "@types/react-dom": "^18.3.0",
     "@types/react-helmet": "^6.1.11",
+    "@types/react-highlight": "^0.12.8",
     "@umijs/fabric": "^2.14.1",
     "@umijs/lint": "^4.2.9",
     "@umijs/max": "^4.2.9",
diff --git a/react-ui/src/pages/Torrent/data.d.ts b/react-ui/src/pages/Torrent/data.d.ts
new file mode 100644
index 0000000..e16c819
--- /dev/null
+++ b/react-ui/src/pages/Torrent/data.d.ts
@@ -0,0 +1,59 @@
+
+
+/** 种子主表 */
+export interface BtTorrent {
+  /** 种子ID */
+  torrentId: number;
+  /** InfoHash */
+  infoHash: string;
+  /** 种子名称 */
+  name: string;
+  /** 总大小(字节) */
+  length: number;
+  /** 每个 piece 的长度 */
+  pieceLength: number;
+  /** piece 总数 */
+  piecesCount: number;
+  /** 创建工具(如 qBittorrent) */
+  createdBy: string;
+  /** .torrent 内的创建时间 */
+  torrentCreateTime: Date | null;
+  /** 上传用户ID(sys_user.userId) */
+  uploaderId: number;
+  /** 上传时间 */
+  uploadTime: Date;
+  /** 种子文件存储路径(服务器端路径或 URL) */
+  filePath: string;
+}
+
+/** 种子文件列表 */
+export interface BtTorrentFile {
+  /** 文件记录ID */
+  id: number;
+  /** 种子ID */
+  torrentId: number;
+  /** 文件路径 */
+  filePath: string;
+  /** 文件大小 */
+  fileSize: number;
+}
+
+/** announce 列表 */
+export interface BtTorrentAnnounce {
+  /** ID */
+  id: number;
+  /** 种子ID */
+  torrentId: number;
+  /** Tracker URL */
+  announceUrl: string;
+}
+
+/** 标签表(可选) */
+export interface BtTorrentTag {
+  /** ID */
+  id: number;
+  /** 种子ID */
+  torrentId: number;
+  /** 标签内容 */
+  tag: string;
+}
diff --git a/react-ui/src/pages/Torrent/index.tsx b/react-ui/src/pages/Torrent/index.tsx
new file mode 100644
index 0000000..6990da7
--- /dev/null
+++ b/react-ui/src/pages/Torrent/index.tsx
@@ -0,0 +1,186 @@
+import React, { useRef, useState } from 'react';
+import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
+import {
+  Button,
+  Modal,
+  message,
+  Drawer,
+  Form,
+  Input,
+  InputNumber,
+  DatePicker,
+  Card,
+  Layout,
+} from 'antd';
+import {
+  ProTable,
+  ActionType,
+  ProColumns,
+  ProDescriptions,
+  ProDescriptionsItemProps,
+} from '@ant-design/pro-components';
+import type { BtTorrent } from './data';
+import {
+  listBtTorrent,
+  getBtTorrent,
+  addBtTorrent,
+  updateBtTorrent,
+  removeBtTorrent,
+} from './service';
+
+const { Content } = Layout;
+
+const BtTorrentPage: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  const [form] = Form.useForm();
+  const [modalVisible, setModalVisible] = useState(false);
+  const [drawerVisible, setDrawerVisible] = useState(false);
+  const [current, setCurrent] = useState<Partial<BtTorrent>>({});
+
+  const columns: ProColumns<BtTorrent>[] = [
+    {
+      title: '种子ID',
+      dataIndex: 'torrentId',
+      hideInForm: true,
+      render: (dom, entity) => (
+        <a
+          onClick={async () => {
+            const res = await getBtTorrent(entity.torrentId!);
+            setCurrent(res);
+            setDrawerVisible(true);
+          }}
+        >
+          {dom}
+        </a>
+      ),
+    },
+    { title: '名称', dataIndex: 'name' },
+    { title: 'infoHash', dataIndex: 'infoHash' },
+    { title: '大小 (bytes)', dataIndex: 'length', valueType: 'digit' },
+    { title: '分片大小', dataIndex: 'pieceLength', valueType: 'digit' },
+    { title: '片段数', dataIndex: 'piecesCount', valueType: 'digit' },
+    { title: '创建工具', dataIndex: 'createdBy', hideInSearch: true },
+    { title: '上传时间', dataIndex: 'uploadTime', valueType: 'dateTime', hideInSearch: true },
+    {
+      title: '操作',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button key="view" type="link" icon={<EyeOutlined />} onClick={() => {
+          setCurrent(record);
+          setDrawerVisible(true);
+        }}>查看</Button>,
+        <Button key="edit" type="link" icon={<EditOutlined />} onClick={() => {
+          setCurrent(record);
+          form.setFieldsValue(record);
+          setModalVisible(true);
+        }}>编辑</Button>,
+        <Button key="delete" type="link" icon={<DeleteOutlined />} danger onClick={() => {
+          Modal.confirm({
+            title: '删除确认',
+            content: '确定删除该种子?',
+            onOk: async () => {
+              await removeBtTorrent([record.torrentId!]);
+              message.success('删除成功');
+              actionRef.current?.reload();
+            },
+          });
+        }}>删除</Button>,
+      ],
+    },
+  ];
+
+  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('新增成功');
+    }
+    setModalVisible(false);
+    form.resetFields();
+    actionRef.current?.reload();
+  };
+
+  return (
+    <Content>
+      <Card bordered={false}>
+        <ProTable<BtTorrent>
+          headerTitle="种子列表"
+          actionRef={actionRef}
+          rowKey="torrentId"
+          search={{ labelWidth: 100 }}
+          toolBarRender={() => [
+            <Button
+              key="add"
+              type="primary"
+              icon={<PlusOutlined />}
+              onClick={() => {
+                form.resetFields();
+                setCurrent({});
+                setModalVisible(true);
+              }}
+            >
+              新增
+            </Button>,
+          ]}
+          request={async (params) => {
+            const res = await listBtTorrent(params);
+            return { data: res.rows || res.data || [], success: true };
+          }}
+          columns={columns}
+        />
+
+        {/* 编辑/新增弹窗 */}
+        <Modal
+          title={current?.torrentId ? '编辑种子' : '新增种子'}
+          open={modalVisible}
+          onOk={handleSubmit}
+          onCancel={() => setModalVisible(false)}
+          destroyOnClose
+        >
+          <Form form={form} layout="vertical">
+            <Form.Item name="name" label="名称" rules={[{ required: true }]}>
+              <Input />
+            </Form.Item>
+            <Form.Item name="infoHash" label="infoHash" rules={[{ required: true }]}>
+              <Input />
+            </Form.Item>
+            <Form.Item name="length" label="总大小 (bytes)" rules={[{ required: true }]}>
+              <InputNumber style={{ width: '100%' }} />
+            </Form.Item>
+            <Form.Item name="pieceLength" label="分片大小" rules={[{ required: true }]}>
+              <InputNumber style={{ width: '100%' }} />
+            </Form.Item>
+            <Form.Item name="piecesCount" label="片段数">
+              <InputNumber style={{ width: '100%' }} />
+            </Form.Item>
+            <Form.Item name="createdBy" label="创建工具">
+              <Input />
+            </Form.Item>
+          </Form>
+        </Modal>
+
+        {/* 详情抽屉 */}
+        <Drawer
+          width={500}
+          open={drawerVisible}
+          onClose={() => setDrawerVisible(false)}
+          title="种子详情"
+        >
+          {current && (
+            <ProDescriptions<BtTorrent>
+              column={1}
+              title={current.name}
+              request={async () => ({ data: current })}
+              columns={columns as ProDescriptionsItemProps<BtTorrent>[]}
+            />
+          )}
+        </Drawer>
+      </Card>
+    </Content>
+  );
+};
+
+export default BtTorrentPage;
diff --git a/react-ui/src/pages/Torrent/service.ts b/react-ui/src/pages/Torrent/service.ts
new file mode 100644
index 0000000..c1d8e26
--- /dev/null
+++ b/react-ui/src/pages/Torrent/service.ts
@@ -0,0 +1,175 @@
+import { request } from '@umijs/max';
+import type {
+  BtTorrent,
+  BtTorrentFile,
+  BtTorrentAnnounce,
+  BtTorrentTag,
+} from '@/pages/Torrent/data'; // 假设你把 data.d.ts 放这里
+
+// ================================
+// 种子主表(bt_torrent)接口
+// ================================
+
+/** 查询种子列表 */
+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 getBtTorrent(torrentId: number) {
+  return request<BtTorrent>(`/api/system/torrent/${torrentId}`, {
+    method: 'get',
+  });
+}
+
+/** 新增种子 */
+export async function addBtTorrent(data: BtTorrent) {
+  return request('/api/system/torrent', {
+    method: 'post',
+    data,
+  });
+}
+
+/** 修改种子 */
+export async function updateBtTorrent(data: BtTorrent) {
+  return request('/api/system/torrent', {
+    method: 'put',
+    data,
+  });
+}
+
+/** 删除种子(支持批量) */
+export async function removeBtTorrent(torrentIds: number[]) {
+  return request(`/api/system/torrent/${torrentIds.join(',')}`, {
+    method: 'delete',
+  });
+}
+
+// ================================
+// 种子文件(bt_torrent_file)接口
+// ================================
+
+/** 查询种子文件列表 */
+export async function listBtTorrentFile(params?: Partial<BtTorrentFile>) {
+  const queryString = params ? `?${new URLSearchParams(params as any)}` : '';
+  return request(`/api/system/file/list${queryString}`, {
+    method: 'get',
+  });
+}
+
+/** 获取文件详情 */
+export async function getBtTorrentFile(id: number) {
+  return request<BtTorrentFile>(`/api/system/file/${id}`, {
+    method: 'get',
+  });
+}
+
+/** 新增文件 */
+export async function addBtTorrentFile(data: BtTorrentFile) {
+  return request('/api/system/file', {
+    method: 'post',
+    data,
+  });
+}
+
+/** 修改文件 */
+export async function updateBtTorrentFile(data: BtTorrentFile) {
+  return request('/api/system/file', {
+    method: 'put',
+    data,
+  });
+}
+
+/** 删除文件(支持批量) */
+export async function removeBtTorrentFile(ids: number[]) {
+  return request(`/api/system/file/${ids.join(',')}`, {
+    method: 'delete',
+  });
+}
+
+// ================================
+// Tracker 列表(bt_torrent_announce)接口
+// ================================
+
+/** 查询 Tracker 列表 */
+export async function listBtTorrentAnnounce(params?: Partial<BtTorrentAnnounce>) {
+  const queryString = params ? `?${new URLSearchParams(params as any)}` : '';
+  return request(`/api/system/announce/list${queryString}`, {
+    method: 'get',
+  });
+}
+
+/** 获取单个 Tracker */
+export async function getBtTorrentAnnounce(id: number) {
+  return request<BtTorrentAnnounce>(`/api/system/announce/${id}`, {
+    method: 'get',
+  });
+}
+
+/** 新增 Tracker */
+export async function addBtTorrentAnnounce(data: BtTorrentAnnounce) {
+  return request('/api/system/announce', {
+    method: 'post',
+    data,
+  });
+}
+
+/** 修改 Tracker */
+export async function updateBtTorrentAnnounce(data: BtTorrentAnnounce) {
+  return request('/api/system/announce', {
+    method: 'put',
+    data,
+  });
+}
+
+/** 删除 Tracker(支持批量) */
+export async function removeBtTorrentAnnounce(ids: number[]) {
+  return request(`/api/system/announce/${ids.join(',')}`, {
+    method: 'delete',
+  });
+}
+
+// ================================
+// 种子标签(bt_torrent_tags)接口
+// ================================
+
+/** 查询标签列表 */
+export async function listBtTorrentTags(params?: Partial<BtTorrentTag>) {
+  const queryString = params ? `?${new URLSearchParams(params as any)}` : '';
+  return request(`/api/system/tags/list${queryString}`, {
+    method: 'get',
+  });
+}
+
+/** 获取标签详情 */
+export async function getBtTorrentTag(id: number) {
+  return request<BtTorrentTag>(`/api/system/tags/${id}`, {
+    method: 'get',
+  });
+}
+
+/** 新增标签 */
+export async function addBtTorrentTag(data: BtTorrentTag) {
+  return request('/api/system/tags', {
+    method: 'post',
+    data,
+  });
+}
+
+/** 修改标签 */
+export async function updateBtTorrentTag(data: BtTorrentTag) {
+  return request('/api/system/tags', {
+    method: 'put',
+    data,
+  });
+}
+
+/** 删除标签(支持批量) */
+export async function removeBtTorrentTag(ids: number[]) {
+  return request(`/api/system/tags/${ids.join(',')}`, {
+    method: 'delete',
+  });
+}
diff --git a/react-ui/src/pages/Tracker/Projects/index.tsx b/react-ui/src/pages/Tracker/Projects/index.tsx
new file mode 100644
index 0000000..b5bf106
--- /dev/null
+++ b/react-ui/src/pages/Tracker/Projects/index.tsx
@@ -0,0 +1,203 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
+import {
+  Button,
+  message,
+  Drawer,
+  Modal,
+  Form,
+  Input,
+  Select,
+  DatePicker,
+  Card,
+  Layout,
+} from 'antd';
+import type { FormInstance } from 'antd';
+import {
+  ProTable,
+  ActionType,
+  ProColumns,
+  FooterToolbar,
+  ProDescriptions,
+  ProDescriptionsItemProps,
+} from '@ant-design/pro-components';
+import type { TrackerProject } from '../data.d.ts';
+import {
+  listTrackerProject,
+  getTrackerProject,
+  addTrackerProject,
+  updateTrackerProject,
+  removeTrackerProject,
+} from '../service';
+
+const { Content } = Layout;
+
+const TrackerProjectPage: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  const [detailVisible, setDetailVisible] = useState(false);
+  const [editVisible, setEditVisible] = useState(false);
+  const [current, setCurrent] = useState<TrackerProject>();
+  const [form] = Form.useForm();
+
+  const columns: ProColumns<TrackerProject>[] = [
+    {
+      title: '项目ID',
+      dataIndex: 'projectId',
+      render: (_, row) => (
+        <a
+          onClick={async () => {
+            const res = await getTrackerProject(row.projectId!);
+            setCurrent(res.data);
+            setDetailVisible(true);
+          }}
+        >
+          {row.projectId}
+        </a>
+      ),
+    },
+    { title: '项目名称', dataIndex: 'projectName' },
+    { title: '描述', dataIndex: 'description', hideInSearch: true },
+    { title: '状态', dataIndex: 'status', valueEnum: { active: { text: '激活' }, inactive: { text: '不活跃' } } },
+    { title: '创建者', dataIndex: 'createBy', hideInSearch: true },
+    { title: '创建时间', dataIndex: 'createTime', hideInSearch: true },
+    { title: '更新时间', dataIndex: 'updateTime', hideInSearch: true },
+    {
+      title: '操作',
+      valueType: 'option',
+      render: (_, row) => [
+        <Button
+          key="view"
+          type="link"
+          icon={<EyeOutlined />}
+          onClick={async () => {
+            const res = await getTrackerProject(row.projectId!);
+            setCurrent(res.data);
+            setDetailVisible(true);
+          }}
+        >
+          查看
+        </Button>,
+        <Button
+          key="edit"
+          type="link"
+          icon={<EditOutlined />}
+          onClick={() => {
+            setCurrent(row);
+            form.setFieldsValue(row);
+            setEditVisible(true);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          key="delete"
+          type="link"
+          icon={<DeleteOutlined />}
+          danger
+          onClick={() => {
+            Modal.confirm({
+              title: '删除项目',
+              content: '确认删除该项目?',
+              onOk: async () => {
+                await removeTrackerProject([row.projectId!]);
+                message.success('删除成功');
+                actionRef.current?.reload();
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  /** 新增或更新 */
+  const handleSubmit = async (values: any) => {
+    if (current?.projectId) {
+      await updateTrackerProject({ ...current, ...values });
+      message.success('更新成功');
+    } else {
+      await addTrackerProject(values);
+      message.success('新增成功');
+    }
+    setEditVisible(false);
+    actionRef.current?.reload();
+  };
+
+  return (
+    <Content>
+      <Card bordered={false}>
+        <ProTable<TrackerProject>
+          headerTitle="项目列表"
+          actionRef={actionRef}
+          rowKey="projectId"
+          search={{ labelWidth: 100 }}
+          toolBarRender={() => [
+            <Button
+              key="add"
+              type="primary"
+              icon={<PlusOutlined />}
+              onClick={() => {
+                form.resetFields();
+                setCurrent(undefined);
+                setEditVisible(true);
+              }}
+            >
+              新增
+            </Button>,
+          ]}
+          request={async (params) => {
+            const res = await listTrackerProject(params);
+            return { data: res.rows, success: true, total: res.total };
+          }}
+          columns={columns}
+        />
+
+        {/* 详情 Drawer */}
+        <Drawer
+          width={500}
+          open={detailVisible}
+          onClose={() => setDetailVisible(false)}
+        >
+          {current && (
+            <ProDescriptions<TrackerProject>
+              column={1}
+              title="项目详情"
+              dataSource={current}
+              columns={columns as ProDescriptionsItemProps<TrackerProject>[]}
+            />
+          )}
+        </Drawer>
+
+        {/* 新增/编辑 Modal */}
+        <Modal
+          title={current?.projectId ? '编辑项目' : '新增项目'}
+          open={editVisible}
+          onCancel={() => setEditVisible(false)}
+          onOk={() => {
+            form.validateFields().then(handleSubmit);
+          }}
+        >
+          <Form form={form} layout="vertical">
+            <Form.Item name="projectName" label="项目名称" rules={[{ required: true }]}>
+              <Input />
+            </Form.Item>
+            <Form.Item name="description" label="描述">
+              <Input.TextArea />
+            </Form.Item>
+            <Form.Item name="status" label="状态" initialValue="active">
+              <Select>
+                <Select.Option value="active">激活</Select.Option>
+                <Select.Option value="inactive">不活跃</Select.Option>
+              </Select>
+            </Form.Item>
+            {/* TODO: 补充其他字段 */}
+          </Form>
+        </Modal>
+      </Card>
+    </Content>
+  );
+};
+
+export default TrackerProjectPage;
diff --git a/react-ui/src/pages/Tracker/data.d.ts b/react-ui/src/pages/Tracker/data.d.ts
new file mode 100644
index 0000000..be71092
--- /dev/null
+++ b/react-ui/src/pages/Tracker/data.d.ts
@@ -0,0 +1,71 @@
+// data.d.ts
+
+/** 项目表 */
+export interface TrackerProject {
+  /** 项目ID */
+  projectId: number;
+  /** 项目名称 */
+  projectName: string;
+  /** 项目描述 */
+  description: string;
+  /** 项目状态(active: 激活, inactive: 不活跃) */
+  status: 'active' | 'inactive';
+  /** 创建者 */
+  createBy: string;
+  /** 创建时间 */
+  createTime: Date | null;
+  /** 更新者 */
+  updateBy: string;
+  /** 更新时间 */
+  updateTime: Date | null;
+}
+
+/** 项目与用户关联表 */
+export interface TrackerProjectUser {
+  /** 项目ID */
+  projectId: number;
+  /** 用户ID */
+  userId: number;
+  /** 角色(管理员、成员等) */
+  role: string;
+  /** 加入时间 */
+  createTime: Date;
+}
+
+/** 任务表 */
+export interface TrackerTask {
+  /** 任务ID */
+  taskId: number;
+  /** 所属项目ID */
+  projectId: number;
+  /** 任务名称 */
+  taskName: string;
+  /** 任务描述 */
+  description: string;
+  /** 分配给的用户ID */
+  assignedTo: number;
+  /** 任务状态(open: 待办, in_progress: 进行中, closed: 完成) */
+  status: 'open' | 'in_progress' | 'closed';
+  /** 任务优先级(low, medium, high) */
+  priority: 'low' | 'medium' | 'high';
+  /** 创建时间 */
+  createTime: Date | null;
+  /** 更新时间 */
+  updateTime: Date | null;
+}
+
+/** 任务日志表 */
+export interface TrackerTaskLog {
+  /** 日志ID */
+  logId: number;
+  /** 任务ID */
+  taskId: number;
+  /** 操作用户ID */
+  userId: number;
+  /** 操作类型(创建、更新、删除) */
+  action: '创建' | '更新' | '删除';
+  /** 操作描述 */
+  description: string;
+  /** 创建时间 */
+  createTime: Date | null;
+}
diff --git a/react-ui/src/pages/Tracker/service.ts b/react-ui/src/pages/Tracker/service.ts
new file mode 100644
index 0000000..c550cf4
--- /dev/null
+++ b/react-ui/src/pages/Tracker/service.ts
@@ -0,0 +1,199 @@
+import { request } from '@umijs/max';
+import type {
+  TrackerProject,
+  TrackerProjectUser,
+  TrackerTask,
+  TrackerTaskLog,
+} from '@/pages/Tracker/data';
+
+// ================================
+// 项目接口
+// ================================
+
+/** 查询项目分页列表 */
+export async function listTrackerProject(params?: TrackerProject) {
+  const queryString = params
+    ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+    : '';
+  return request(`/api/tracker/project/list${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+/** 获取单个项目详情 */
+export async function getTrackerProject(projectId: number) {
+  return request<TrackerProject>(`/api/tracker/project/${projectId}`, {
+    method: 'get',
+  });
+}
+
+/** 新增项目 */
+export async function addTrackerProject(data: TrackerProject) {
+  return request('/api/tracker/project', {
+    data,
+    method: 'post',
+  });
+}
+
+/** 更新项目 */
+export async function updateTrackerProject(data: TrackerProject) {
+  return request('/api/tracker/project', {
+    data,
+    method: 'put',
+  });
+}
+
+/** 删除项目(支持批量,传入 ID 数组) */
+export async function removeTrackerProject(projectIds: number[]) {
+  return request(`/api/tracker/project/${projectIds.join(',')}`, {
+    method: 'delete',
+  });
+}
+
+// ================================
+// 项目与用户关联接口
+// ================================
+
+/** 查询项目-用户关联分页列表 */
+export async function listTrackerProjectUser(params?: TrackerProjectUser) {
+  const queryString = params
+    ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+    : '';
+  return request(`/api/tracker/user/list${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+/** 获取单个项目-用户关联详情 */
+export async function getTrackerProjectUser(projectId: number) {
+  return request<TrackerProjectUser>(`/api/tracker/user/${projectId}`, {
+    method: 'get',
+  });
+}
+
+/** 新增项目-用户关联 */
+export async function addTrackerProjectUser(data: TrackerProjectUser) {
+  return request('/api/tracker/user', {
+    data,
+    method: 'post',
+  });
+}
+
+/** 更新项目-用户关联 */
+export async function updateTrackerProjectUser(data: TrackerProjectUser) {
+  return request('/api/tracker/user', {
+    data,
+    method: 'put',
+  });
+}
+
+/** 删除项目-用户关联(支持批量,传入项目 ID 数组) */
+export async function removeTrackerProjectUser(projectIds: number[]) {
+  return request(`/api/tracker/user/${projectIds.join(',')}`, {
+    method: 'delete',
+  });
+}
+
+// ================================
+// 任务接口
+// ================================
+
+/** 查询任务分页列表 */
+export async function listTrackerTask(params?: TrackerTask) {
+  const queryString = params
+    ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+    : '';
+  return request(`/api/tracker/task/list${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+/** 获取单个任务详情 */
+export async function getTrackerTask(taskId: number) {
+  return request<TrackerTask>(`/api/tracker/task/${taskId}`, {
+    method: 'get',
+  });
+}
+
+/** 新增任务 */
+export async function addTrackerTask(data: TrackerTask) {
+  return request('/api/tracker/task', {
+    data,
+    method: 'post',
+  });
+}
+
+/** 更新任务 */
+export async function updateTrackerTask(data: TrackerTask) {
+  return request('/api/tracker/task', {
+    data,
+    method: 'put',
+  });
+}
+
+/** 删除任务(支持批量,传入任务 ID 数组) */
+export async function removeTrackerTask(taskIds: number[]) {
+  return request(`/api/tracker/task/${taskIds.join(',')}`, {
+    method: 'delete',
+  });
+}
+
+// ================================
+// 任务日志接口
+// ================================
+
+/** 查询任务日志分页列表 */
+export async function listTrackerTaskLog(params?: TrackerTaskLog) {
+  const queryString = params
+    ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+    : '';
+  return request(`/api/tracker/log/list${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+/** 获取单个任务日志详情 */
+export async function getTrackerTaskLog(logId: number) {
+  return request<TrackerTaskLog>(`/api/tracker/log/${logId}`, {
+    method: 'get',
+  });
+}
+
+/** 新增任务日志 */
+export async function addTrackerTaskLog(data: TrackerTaskLog) {
+  return request('/api/tracker/log', {
+    data,
+    method: 'post',
+  });
+}
+
+/** 更新任务日志 */
+export async function updateTrackerTaskLog(data: TrackerTaskLog) {
+  return request('/api/tracker/log', {
+    data,
+    method: 'put',
+  });
+}
+
+/** 删除任务日志(支持批量,传入日志 ID 数组) */
+export async function removeTrackerTaskLog(logIds: number[]) {
+  return request(`/api/tracker/log/${logIds.join(',')}`, {
+    method: 'delete',
+  });
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentAnnounceController.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentAnnounceController.java
new file mode 100644
index 0000000..b41035e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentAnnounceController.java
@@ -0,0 +1,105 @@
+package com.ruoyi.torrent.controller;
+
+import java.util.List;
+
+import com.ruoyi.torrent.service.IBtTorrentAnnounceService;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.torrent.domain.BtTorrentAnnounce;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 种子的 Tracker 列Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@RestController
+@RequestMapping("/system/announce")
+public class BtTorrentAnnounceController extends BaseController
+{
+    @Autowired
+    private IBtTorrentAnnounceService btTorrentAnnounceService;
+
+    /**
+     * 查询种子的 Tracker 列列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:announce:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BtTorrentAnnounce btTorrentAnnounce)
+    {
+        startPage();
+        List<BtTorrentAnnounce> list = btTorrentAnnounceService.selectBtTorrentAnnounceList(btTorrentAnnounce);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出种子的 Tracker 列列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:announce:export')")
+    @Log(title = "种子的 Tracker 列", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BtTorrentAnnounce btTorrentAnnounce)
+    {
+        List<BtTorrentAnnounce> list = btTorrentAnnounceService.selectBtTorrentAnnounceList(btTorrentAnnounce);
+        ExcelUtil<BtTorrentAnnounce> util = new ExcelUtil<BtTorrentAnnounce>(BtTorrentAnnounce.class);
+        util.exportExcel(response, list, "种子的 Tracker 列数据");
+    }
+
+    /**
+     * 获取种子的 Tracker 列详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:announce:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(btTorrentAnnounceService.selectBtTorrentAnnounceById(id));
+    }
+
+    /**
+     * 新增种子的 Tracker 列
+     */
+    @PreAuthorize("@ss.hasPermi('system:announce:add')")
+    @Log(title = "种子的 Tracker 列", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BtTorrentAnnounce btTorrentAnnounce)
+    {
+        return toAjax(btTorrentAnnounceService.insertBtTorrentAnnounce(btTorrentAnnounce));
+    }
+
+    /**
+     * 修改种子的 Tracker 列
+     */
+    @PreAuthorize("@ss.hasPermi('system:announce:edit')")
+    @Log(title = "种子的 Tracker 列", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BtTorrentAnnounce btTorrentAnnounce)
+    {
+        return toAjax(btTorrentAnnounceService.updateBtTorrentAnnounce(btTorrentAnnounce));
+    }
+
+    /**
+     * 删除种子的 Tracker 列
+     */
+    @PreAuthorize("@ss.hasPermi('system:announce:remove')")
+    @Log(title = "种子的 Tracker 列", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(btTorrentAnnounceService.deleteBtTorrentAnnounceByIds(ids));
+    }
+}
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
new file mode 100644
index 0000000..2dab95a
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
@@ -0,0 +1,105 @@
+package com.ruoyi.torrent.controller;
+
+import java.util.List;
+
+import com.ruoyi.torrent.service.IBtTorrentService;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.torrent.domain.BtTorrent;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 种子主Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@RestController
+@RequestMapping("/system/torrent")
+public class BtTorrentController extends BaseController
+{
+    @Autowired
+    private IBtTorrentService btTorrentService;
+
+    /**
+     * 查询种子主列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BtTorrent btTorrent)
+    {
+        startPage();
+        List<BtTorrent> list = btTorrentService.selectBtTorrentList(btTorrent);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出种子主列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:export')")
+    @Log(title = "种子主", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BtTorrent btTorrent)
+    {
+        List<BtTorrent> list = btTorrentService.selectBtTorrentList(btTorrent);
+        ExcelUtil<BtTorrent> util = new ExcelUtil<BtTorrent>(BtTorrent.class);
+        util.exportExcel(response, list, "种子主数据");
+    }
+
+    /**
+     * 获取种子主详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:query')")
+    @GetMapping(value = "/{torrentId}")
+    public AjaxResult getInfo(@PathVariable("torrentId") Long torrentId)
+    {
+        return success(btTorrentService.selectBtTorrentByTorrentId(torrentId));
+    }
+
+    /**
+     * 新增种子主
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:add')")
+    @Log(title = "种子主", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BtTorrent btTorrent)
+    {
+        return toAjax(btTorrentService.insertBtTorrent(btTorrent));
+    }
+
+    /**
+     * 修改种子主
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:edit')")
+    @Log(title = "种子主", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BtTorrent btTorrent)
+    {
+        return toAjax(btTorrentService.updateBtTorrent(btTorrent));
+    }
+
+    /**
+     * 删除种子主
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:remove')")
+    @Log(title = "种子主", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{torrentIds}")
+    public AjaxResult remove(@PathVariable Long[] torrentIds)
+    {
+        return toAjax(btTorrentService.deleteBtTorrentByTorrentIds(torrentIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentFileController.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentFileController.java
new file mode 100644
index 0000000..3c731ee
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentFileController.java
@@ -0,0 +1,104 @@
+package com.ruoyi.torrent.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.torrent.domain.BtTorrentFile;
+import com.ruoyi.torrent.service.IBtTorrentFileService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 种子包含的文件列Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@RestController
+@RequestMapping("/system/file")
+public class BtTorrentFileController extends BaseController
+{
+    @Autowired
+    private IBtTorrentFileService btTorrentFileService;
+
+    /**
+     * 查询种子包含的文件列列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:file:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BtTorrentFile btTorrentFile)
+    {
+        startPage();
+        List<BtTorrentFile> list = btTorrentFileService.selectBtTorrentFileList(btTorrentFile);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出种子包含的文件列列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:file:export')")
+    @Log(title = "种子包含的文件列", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BtTorrentFile btTorrentFile)
+    {
+        List<BtTorrentFile> list = btTorrentFileService.selectBtTorrentFileList(btTorrentFile);
+        ExcelUtil<BtTorrentFile> util = new ExcelUtil<BtTorrentFile>(BtTorrentFile.class);
+        util.exportExcel(response, list, "种子包含的文件列数据");
+    }
+
+    /**
+     * 获取种子包含的文件列详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:file:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(btTorrentFileService.selectBtTorrentFileById(id));
+    }
+
+    /**
+     * 新增种子包含的文件列
+     */
+    @PreAuthorize("@ss.hasPermi('system:file:add')")
+    @Log(title = "种子包含的文件列", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BtTorrentFile btTorrentFile)
+    {
+        return toAjax(btTorrentFileService.insertBtTorrentFile(btTorrentFile));
+    }
+
+    /**
+     * 修改种子包含的文件列
+     */
+    @PreAuthorize("@ss.hasPermi('system:file:edit')")
+    @Log(title = "种子包含的文件列", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BtTorrentFile btTorrentFile)
+    {
+        return toAjax(btTorrentFileService.updateBtTorrentFile(btTorrentFile));
+    }
+
+    /**
+     * 删除种子包含的文件列
+     */
+    @PreAuthorize("@ss.hasPermi('system:file:remove')")
+    @Log(title = "种子包含的文件列", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(btTorrentFileService.deleteBtTorrentFileByIds(ids));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentTagsController.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentTagsController.java
new file mode 100644
index 0000000..9b0fb2c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentTagsController.java
@@ -0,0 +1,104 @@
+package com.ruoyi.torrent.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.torrent.domain.BtTorrentTags;
+import com.ruoyi.torrent.service.IBtTorrentTagsService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 种子标签Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@RestController
+@RequestMapping("/system/tags")
+public class BtTorrentTagsController extends BaseController
+{
+    @Autowired
+    private IBtTorrentTagsService btTorrentTagsService;
+
+    /**
+     * 查询种子标签列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:tags:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BtTorrentTags btTorrentTags)
+    {
+        startPage();
+        List<BtTorrentTags> list = btTorrentTagsService.selectBtTorrentTagsList(btTorrentTags);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出种子标签列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:tags:export')")
+    @Log(title = "种子标签", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BtTorrentTags btTorrentTags)
+    {
+        List<BtTorrentTags> list = btTorrentTagsService.selectBtTorrentTagsList(btTorrentTags);
+        ExcelUtil<BtTorrentTags> util = new ExcelUtil<BtTorrentTags>(BtTorrentTags.class);
+        util.exportExcel(response, list, "种子标签数据");
+    }
+
+    /**
+     * 获取种子标签详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:tags:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(btTorrentTagsService.selectBtTorrentTagsById(id));
+    }
+
+    /**
+     * 新增种子标签
+     */
+    @PreAuthorize("@ss.hasPermi('system:tags:add')")
+    @Log(title = "种子标签", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BtTorrentTags btTorrentTags)
+    {
+        return toAjax(btTorrentTagsService.insertBtTorrentTags(btTorrentTags));
+    }
+
+    /**
+     * 修改种子标签
+     */
+    @PreAuthorize("@ss.hasPermi('system:tags:edit')")
+    @Log(title = "种子标签", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BtTorrentTags btTorrentTags)
+    {
+        return toAjax(btTorrentTagsService.updateBtTorrentTags(btTorrentTags));
+    }
+
+    /**
+     * 删除种子标签
+     */
+    @PreAuthorize("@ss.hasPermi('system:tags:remove')")
+    @Log(title = "种子标签", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(btTorrentTagsService.deleteBtTorrentTagsByIds(ids));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrent.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrent.java
new file mode 100644
index 0000000..9a85f2f
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrent.java
@@ -0,0 +1,181 @@
+package com.ruoyi.torrent.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 种子主对象 bt_torrent
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public class BtTorrent extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 种子ID */
+    private Long torrentId;
+
+    /** InfoHash */
+    @Excel(name = "InfoHash")
+    private String infoHash;
+
+    /** 种子名称 */
+    @Excel(name = "种子名称")
+    private String name;
+
+    /** 总大小(字节) */
+    @Excel(name = "总大小", readConverterExp = "字=节")
+    private Long length;
+
+    /** 每个 piece 的长度 */
+    @Excel(name = "每个 piece 的长度")
+    private Long pieceLength;
+
+    /** piece 总数 */
+    @Excel(name = "piece 总数")
+    private Long piecesCount;
+
+    /** 创建工具 */
+    @Excel(name = "创建工具")
+    private String createdBy;
+
+    /** 种子内记录的创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "种子内记录的创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date torrentCreateTime;
+
+    /** 上传用户ID(关联 sys_user.user_id) */
+    @Excel(name = "上传用户ID", readConverterExp = "关=联,s=ys_user.user_id")
+    private Long uploaderId;
+
+    /** 上传时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "上传时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date uploadTime;
+
+    /** 种子文件在服务器的存储路径 */
+    @Excel(name = "种子文件在服务器的存储路径")
+    private String filePath;
+
+    public void setTorrentId(Long torrentId) 
+    {
+        this.torrentId = torrentId;
+    }
+
+    public Long getTorrentId() 
+    {
+        return torrentId;
+    }
+    public void setInfoHash(String infoHash) 
+    {
+        this.infoHash = infoHash;
+    }
+
+    public String getInfoHash() 
+    {
+        return infoHash;
+    }
+    public void setName(String name) 
+    {
+        this.name = name;
+    }
+
+    public String getName() 
+    {
+        return name;
+    }
+    public void setLength(Long length) 
+    {
+        this.length = length;
+    }
+
+    public Long getLength() 
+    {
+        return length;
+    }
+    public void setPieceLength(Long pieceLength) 
+    {
+        this.pieceLength = pieceLength;
+    }
+
+    public Long getPieceLength() 
+    {
+        return pieceLength;
+    }
+    public void setPiecesCount(Long piecesCount) 
+    {
+        this.piecesCount = piecesCount;
+    }
+
+    public Long getPiecesCount() 
+    {
+        return piecesCount;
+    }
+    public void setCreatedBy(String createdBy) 
+    {
+        this.createdBy = createdBy;
+    }
+
+    public String getCreatedBy() 
+    {
+        return createdBy;
+    }
+    public void setTorrentCreateTime(Date torrentCreateTime) 
+    {
+        this.torrentCreateTime = torrentCreateTime;
+    }
+
+    public Date getTorrentCreateTime() 
+    {
+        return torrentCreateTime;
+    }
+    public void setUploaderId(Long uploaderId) 
+    {
+        this.uploaderId = uploaderId;
+    }
+
+    public Long getUploaderId() 
+    {
+        return uploaderId;
+    }
+    public void setUploadTime(Date uploadTime) 
+    {
+        this.uploadTime = uploadTime;
+    }
+
+    public Date getUploadTime() 
+    {
+        return uploadTime;
+    }
+    public void setFilePath(String filePath) 
+    {
+        this.filePath = filePath;
+    }
+
+    public String getFilePath() 
+    {
+        return filePath;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("torrentId", getTorrentId())
+            .append("infoHash", getInfoHash())
+            .append("name", getName())
+            .append("length", getLength())
+            .append("pieceLength", getPieceLength())
+            .append("piecesCount", getPiecesCount())
+            .append("createdBy", getCreatedBy())
+            .append("torrentCreateTime", getTorrentCreateTime())
+            .append("uploaderId", getUploaderId())
+            .append("uploadTime", getUploadTime())
+            .append("filePath", getFilePath())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentAnnounce.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentAnnounce.java
new file mode 100644
index 0000000..967cc5d
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentAnnounce.java
@@ -0,0 +1,65 @@
+package com.ruoyi.torrent.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 种子的 Tracker 列对象 bt_torrent_announce
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public class BtTorrentAnnounce extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    private Long id;
+
+    /** 种子ID */
+    @Excel(name = "种子ID")
+    private Long torrentId;
+
+    /** Tracker announce URL */
+    @Excel(name = "Tracker announce URL")
+    private String announceUrl;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+    public void setTorrentId(Long torrentId) 
+    {
+        this.torrentId = torrentId;
+    }
+
+    public Long getTorrentId() 
+    {
+        return torrentId;
+    }
+    public void setAnnounceUrl(String announceUrl) 
+    {
+        this.announceUrl = announceUrl;
+    }
+
+    public String getAnnounceUrl() 
+    {
+        return announceUrl;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("torrentId", getTorrentId())
+            .append("announceUrl", getAnnounceUrl())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentFile.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentFile.java
new file mode 100644
index 0000000..920d871
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentFile.java
@@ -0,0 +1,79 @@
+package com.ruoyi.torrent.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 种子包含的文件列对象 bt_torrent_file
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public class BtTorrentFile extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    private Long id;
+
+    /** 种子ID(关联 bt_torrent) */
+    @Excel(name = "种子ID", readConverterExp = "关=联,b=t_torrent")
+    private Long torrentId;
+
+    /** 文件路径 */
+    @Excel(name = "文件路径")
+    private String filePath;
+
+    /** 文件大小(字节) */
+    @Excel(name = "文件大小", readConverterExp = "字=节")
+    private Long fileSize;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+    public void setTorrentId(Long torrentId) 
+    {
+        this.torrentId = torrentId;
+    }
+
+    public Long getTorrentId() 
+    {
+        return torrentId;
+    }
+    public void setFilePath(String filePath) 
+    {
+        this.filePath = filePath;
+    }
+
+    public String getFilePath() 
+    {
+        return filePath;
+    }
+    public void setFileSize(Long fileSize) 
+    {
+        this.fileSize = fileSize;
+    }
+
+    public Long getFileSize() 
+    {
+        return fileSize;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("torrentId", getTorrentId())
+            .append("filePath", getFilePath())
+            .append("fileSize", getFileSize())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentTags.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentTags.java
new file mode 100644
index 0000000..8f19b72
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/domain/BtTorrentTags.java
@@ -0,0 +1,65 @@
+package com.ruoyi.torrent.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 种子标签对象 bt_torrent_tags
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public class BtTorrentTags extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Long torrentId;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String tag;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+    public void setTorrentId(Long torrentId) 
+    {
+        this.torrentId = torrentId;
+    }
+
+    public Long getTorrentId() 
+    {
+        return torrentId;
+    }
+    public void setTag(String tag) 
+    {
+        this.tag = tag;
+    }
+
+    public String getTag() 
+    {
+        return tag;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("torrentId", getTorrentId())
+            .append("tag", getTag())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentAnnounceMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentAnnounceMapper.java
new file mode 100644
index 0000000..47c8478
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentAnnounceMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.mapper;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrentAnnounce;
+
+/**
+ * 种子的 Tracker 列Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface BtTorrentAnnounceMapper 
+{
+    /**
+     * 查询种子的 Tracker 列
+     * 
+     * @param id 种子的 Tracker 列主键
+     * @return 种子的 Tracker 列
+     */
+    public BtTorrentAnnounce selectBtTorrentAnnounceById(Long id);
+
+    /**
+     * 查询种子的 Tracker 列列表
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 种子的 Tracker 列集合
+     */
+    public List<BtTorrentAnnounce> selectBtTorrentAnnounceList(BtTorrentAnnounce btTorrentAnnounce);
+
+    /**
+     * 新增种子的 Tracker 列
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 结果
+     */
+    public int insertBtTorrentAnnounce(BtTorrentAnnounce btTorrentAnnounce);
+
+    /**
+     * 修改种子的 Tracker 列
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 结果
+     */
+    public int updateBtTorrentAnnounce(BtTorrentAnnounce btTorrentAnnounce);
+
+    /**
+     * 删除种子的 Tracker 列
+     * 
+     * @param id 种子的 Tracker 列主键
+     * @return 结果
+     */
+    public int deleteBtTorrentAnnounceById(Long id);
+
+    /**
+     * 批量删除种子的 Tracker 列
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentAnnounceByIds(Long[] ids);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentFileMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentFileMapper.java
new file mode 100644
index 0000000..74012dd
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentFileMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.mapper;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrentFile;
+
+/**
+ * 种子包含的文件列Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface BtTorrentFileMapper 
+{
+    /**
+     * 查询种子包含的文件列
+     * 
+     * @param id 种子包含的文件列主键
+     * @return 种子包含的文件列
+     */
+    public BtTorrentFile selectBtTorrentFileById(Long id);
+
+    /**
+     * 查询种子包含的文件列列表
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 种子包含的文件列集合
+     */
+    public List<BtTorrentFile> selectBtTorrentFileList(BtTorrentFile btTorrentFile);
+
+    /**
+     * 新增种子包含的文件列
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 结果
+     */
+    public int insertBtTorrentFile(BtTorrentFile btTorrentFile);
+
+    /**
+     * 修改种子包含的文件列
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 结果
+     */
+    public int updateBtTorrentFile(BtTorrentFile btTorrentFile);
+
+    /**
+     * 删除种子包含的文件列
+     * 
+     * @param id 种子包含的文件列主键
+     * @return 结果
+     */
+    public int deleteBtTorrentFileById(Long id);
+
+    /**
+     * 批量删除种子包含的文件列
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentFileByIds(Long[] ids);
+}
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
new file mode 100644
index 0000000..180f052
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.mapper;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrent;
+
+/**
+ * 种子主Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface BtTorrentMapper 
+{
+    /**
+     * 查询种子主
+     * 
+     * @param torrentId 种子主主键
+     * @return 种子主
+     */
+    public BtTorrent selectBtTorrentByTorrentId(Long torrentId);
+
+    /**
+     * 查询种子主列表
+     * 
+     * @param btTorrent 种子主
+     * @return 种子主集合
+     */
+    public List<BtTorrent> selectBtTorrentList(BtTorrent btTorrent);
+
+    /**
+     * 新增种子主
+     * 
+     * @param btTorrent 种子主
+     * @return 结果
+     */
+    public int insertBtTorrent(BtTorrent btTorrent);
+
+    /**
+     * 修改种子主
+     * 
+     * @param btTorrent 种子主
+     * @return 结果
+     */
+    public int updateBtTorrent(BtTorrent btTorrent);
+
+    /**
+     * 删除种子主
+     * 
+     * @param torrentId 种子主主键
+     * @return 结果
+     */
+    public int deleteBtTorrentByTorrentId(Long torrentId);
+
+    /**
+     * 批量删除种子主
+     * 
+     * @param torrentIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentByTorrentIds(Long[] torrentIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentTagsMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentTagsMapper.java
new file mode 100644
index 0000000..e9aad12
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/mapper/BtTorrentTagsMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.mapper;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrentTags;
+
+/**
+ * 种子标签Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface BtTorrentTagsMapper 
+{
+    /**
+     * 查询种子标签
+     * 
+     * @param id 种子标签主键
+     * @return 种子标签
+     */
+    public BtTorrentTags selectBtTorrentTagsById(Long id);
+
+    /**
+     * 查询种子标签列表
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 种子标签集合
+     */
+    public List<BtTorrentTags> selectBtTorrentTagsList(BtTorrentTags btTorrentTags);
+
+    /**
+     * 新增种子标签
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 结果
+     */
+    public int insertBtTorrentTags(BtTorrentTags btTorrentTags);
+
+    /**
+     * 修改种子标签
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 结果
+     */
+    public int updateBtTorrentTags(BtTorrentTags btTorrentTags);
+
+    /**
+     * 删除种子标签
+     * 
+     * @param id 种子标签主键
+     * @return 结果
+     */
+    public int deleteBtTorrentTagsById(Long id);
+
+    /**
+     * 批量删除种子标签
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentTagsByIds(Long[] ids);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentAnnounceService.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentAnnounceService.java
new file mode 100644
index 0000000..9f4fb34
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentAnnounceService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.service;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrentAnnounce;
+
+/**
+ * 种子的 Tracker 列Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface IBtTorrentAnnounceService 
+{
+    /**
+     * 查询种子的 Tracker 列
+     * 
+     * @param id 种子的 Tracker 列主键
+     * @return 种子的 Tracker 列
+     */
+    public BtTorrentAnnounce selectBtTorrentAnnounceById(Long id);
+
+    /**
+     * 查询种子的 Tracker 列列表
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 种子的 Tracker 列集合
+     */
+    public List<BtTorrentAnnounce> selectBtTorrentAnnounceList(BtTorrentAnnounce btTorrentAnnounce);
+
+    /**
+     * 新增种子的 Tracker 列
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 结果
+     */
+    public int insertBtTorrentAnnounce(BtTorrentAnnounce btTorrentAnnounce);
+
+    /**
+     * 修改种子的 Tracker 列
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 结果
+     */
+    public int updateBtTorrentAnnounce(BtTorrentAnnounce btTorrentAnnounce);
+
+    /**
+     * 批量删除种子的 Tracker 列
+     * 
+     * @param ids 需要删除的种子的 Tracker 列主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentAnnounceByIds(Long[] ids);
+
+    /**
+     * 删除种子的 Tracker 列信息
+     * 
+     * @param id 种子的 Tracker 列主键
+     * @return 结果
+     */
+    public int deleteBtTorrentAnnounceById(Long id);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentFileService.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentFileService.java
new file mode 100644
index 0000000..2fe14d7
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentFileService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.service;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrentFile;
+
+/**
+ * 种子包含的文件列Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface IBtTorrentFileService 
+{
+    /**
+     * 查询种子包含的文件列
+     * 
+     * @param id 种子包含的文件列主键
+     * @return 种子包含的文件列
+     */
+    public BtTorrentFile selectBtTorrentFileById(Long id);
+
+    /**
+     * 查询种子包含的文件列列表
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 种子包含的文件列集合
+     */
+    public List<BtTorrentFile> selectBtTorrentFileList(BtTorrentFile btTorrentFile);
+
+    /**
+     * 新增种子包含的文件列
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 结果
+     */
+    public int insertBtTorrentFile(BtTorrentFile btTorrentFile);
+
+    /**
+     * 修改种子包含的文件列
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 结果
+     */
+    public int updateBtTorrentFile(BtTorrentFile btTorrentFile);
+
+    /**
+     * 批量删除种子包含的文件列
+     * 
+     * @param ids 需要删除的种子包含的文件列主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentFileByIds(Long[] ids);
+
+    /**
+     * 删除种子包含的文件列信息
+     * 
+     * @param id 种子包含的文件列主键
+     * @return 结果
+     */
+    public int deleteBtTorrentFileById(Long id);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentService.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentService.java
new file mode 100644
index 0000000..e741711
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.service;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrent;
+
+/**
+ * 种子主Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface IBtTorrentService 
+{
+    /**
+     * 查询种子主
+     * 
+     * @param torrentId 种子主主键
+     * @return 种子主
+     */
+    public BtTorrent selectBtTorrentByTorrentId(Long torrentId);
+
+    /**
+     * 查询种子主列表
+     * 
+     * @param btTorrent 种子主
+     * @return 种子主集合
+     */
+    public List<BtTorrent> selectBtTorrentList(BtTorrent btTorrent);
+
+    /**
+     * 新增种子主
+     * 
+     * @param btTorrent 种子主
+     * @return 结果
+     */
+    public int insertBtTorrent(BtTorrent btTorrent);
+
+    /**
+     * 修改种子主
+     * 
+     * @param btTorrent 种子主
+     * @return 结果
+     */
+    public int updateBtTorrent(BtTorrent btTorrent);
+
+    /**
+     * 批量删除种子主
+     * 
+     * @param torrentIds 需要删除的种子主主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentByTorrentIds(Long[] torrentIds);
+
+    /**
+     * 删除种子主信息
+     * 
+     * @param torrentId 种子主主键
+     * @return 结果
+     */
+    public int deleteBtTorrentByTorrentId(Long torrentId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentTagsService.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentTagsService.java
new file mode 100644
index 0000000..375e472
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/IBtTorrentTagsService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.torrent.service;
+
+import java.util.List;
+import com.ruoyi.torrent.domain.BtTorrentTags;
+
+/**
+ * 种子标签Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+public interface IBtTorrentTagsService 
+{
+    /**
+     * 查询种子标签
+     * 
+     * @param id 种子标签主键
+     * @return 种子标签
+     */
+    public BtTorrentTags selectBtTorrentTagsById(Long id);
+
+    /**
+     * 查询种子标签列表
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 种子标签集合
+     */
+    public List<BtTorrentTags> selectBtTorrentTagsList(BtTorrentTags btTorrentTags);
+
+    /**
+     * 新增种子标签
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 结果
+     */
+    public int insertBtTorrentTags(BtTorrentTags btTorrentTags);
+
+    /**
+     * 修改种子标签
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 结果
+     */
+    public int updateBtTorrentTags(BtTorrentTags btTorrentTags);
+
+    /**
+     * 批量删除种子标签
+     * 
+     * @param ids 需要删除的种子标签主键集合
+     * @return 结果
+     */
+    public int deleteBtTorrentTagsByIds(Long[] ids);
+
+    /**
+     * 删除种子标签信息
+     * 
+     * @param id 种子标签主键
+     * @return 结果
+     */
+    public int deleteBtTorrentTagsById(Long id);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentAnnounceServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentAnnounceServiceImpl.java
new file mode 100644
index 0000000..70e41a0
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentAnnounceServiceImpl.java
@@ -0,0 +1,93 @@
+package com.ruoyi.torrent.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.torrent.mapper.BtTorrentAnnounceMapper;
+import com.ruoyi.torrent.domain.BtTorrentAnnounce;
+import com.ruoyi.torrent.service.IBtTorrentAnnounceService;
+
+/**
+ * 种子的 Tracker 列Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@Service
+public class BtTorrentAnnounceServiceImpl implements IBtTorrentAnnounceService 
+{
+    @Autowired
+    private BtTorrentAnnounceMapper btTorrentAnnounceMapper;
+
+    /**
+     * 查询种子的 Tracker 列
+     * 
+     * @param id 种子的 Tracker 列主键
+     * @return 种子的 Tracker 列
+     */
+    @Override
+    public BtTorrentAnnounce selectBtTorrentAnnounceById(Long id)
+    {
+        return btTorrentAnnounceMapper.selectBtTorrentAnnounceById(id);
+    }
+
+    /**
+     * 查询种子的 Tracker 列列表
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 种子的 Tracker 列
+     */
+    @Override
+    public List<BtTorrentAnnounce> selectBtTorrentAnnounceList(BtTorrentAnnounce btTorrentAnnounce)
+    {
+        return btTorrentAnnounceMapper.selectBtTorrentAnnounceList(btTorrentAnnounce);
+    }
+
+    /**
+     * 新增种子的 Tracker 列
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 结果
+     */
+    @Override
+    public int insertBtTorrentAnnounce(BtTorrentAnnounce btTorrentAnnounce)
+    {
+        return btTorrentAnnounceMapper.insertBtTorrentAnnounce(btTorrentAnnounce);
+    }
+
+    /**
+     * 修改种子的 Tracker 列
+     * 
+     * @param btTorrentAnnounce 种子的 Tracker 列
+     * @return 结果
+     */
+    @Override
+    public int updateBtTorrentAnnounce(BtTorrentAnnounce btTorrentAnnounce)
+    {
+        return btTorrentAnnounceMapper.updateBtTorrentAnnounce(btTorrentAnnounce);
+    }
+
+    /**
+     * 批量删除种子的 Tracker 列
+     * 
+     * @param ids 需要删除的种子的 Tracker 列主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentAnnounceByIds(Long[] ids)
+    {
+        return btTorrentAnnounceMapper.deleteBtTorrentAnnounceByIds(ids);
+    }
+
+    /**
+     * 删除种子的 Tracker 列信息
+     * 
+     * @param id 种子的 Tracker 列主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentAnnounceById(Long id)
+    {
+        return btTorrentAnnounceMapper.deleteBtTorrentAnnounceById(id);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentFileServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentFileServiceImpl.java
new file mode 100644
index 0000000..6eb696e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentFileServiceImpl.java
@@ -0,0 +1,93 @@
+package com.ruoyi.torrent.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.torrent.mapper.BtTorrentFileMapper;
+import com.ruoyi.torrent.domain.BtTorrentFile;
+import com.ruoyi.torrent.service.IBtTorrentFileService;
+
+/**
+ * 种子包含的文件列Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@Service
+public class BtTorrentFileServiceImpl implements IBtTorrentFileService 
+{
+    @Autowired
+    private BtTorrentFileMapper btTorrentFileMapper;
+
+    /**
+     * 查询种子包含的文件列
+     * 
+     * @param id 种子包含的文件列主键
+     * @return 种子包含的文件列
+     */
+    @Override
+    public BtTorrentFile selectBtTorrentFileById(Long id)
+    {
+        return btTorrentFileMapper.selectBtTorrentFileById(id);
+    }
+
+    /**
+     * 查询种子包含的文件列列表
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 种子包含的文件列
+     */
+    @Override
+    public List<BtTorrentFile> selectBtTorrentFileList(BtTorrentFile btTorrentFile)
+    {
+        return btTorrentFileMapper.selectBtTorrentFileList(btTorrentFile);
+    }
+
+    /**
+     * 新增种子包含的文件列
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 结果
+     */
+    @Override
+    public int insertBtTorrentFile(BtTorrentFile btTorrentFile)
+    {
+        return btTorrentFileMapper.insertBtTorrentFile(btTorrentFile);
+    }
+
+    /**
+     * 修改种子包含的文件列
+     * 
+     * @param btTorrentFile 种子包含的文件列
+     * @return 结果
+     */
+    @Override
+    public int updateBtTorrentFile(BtTorrentFile btTorrentFile)
+    {
+        return btTorrentFileMapper.updateBtTorrentFile(btTorrentFile);
+    }
+
+    /**
+     * 批量删除种子包含的文件列
+     * 
+     * @param ids 需要删除的种子包含的文件列主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentFileByIds(Long[] ids)
+    {
+        return btTorrentFileMapper.deleteBtTorrentFileByIds(ids);
+    }
+
+    /**
+     * 删除种子包含的文件列信息
+     * 
+     * @param id 种子包含的文件列主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentFileById(Long id)
+    {
+        return btTorrentFileMapper.deleteBtTorrentFileById(id);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentServiceImpl.java
new file mode 100644
index 0000000..3b714f2
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentServiceImpl.java
@@ -0,0 +1,93 @@
+package com.ruoyi.torrent.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.torrent.mapper.BtTorrentMapper;
+import com.ruoyi.torrent.domain.BtTorrent;
+import com.ruoyi.torrent.service.IBtTorrentService;
+
+/**
+ * 种子主Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@Service
+public class BtTorrentServiceImpl implements IBtTorrentService 
+{
+    @Autowired
+    private BtTorrentMapper btTorrentMapper;
+
+    /**
+     * 查询种子主
+     * 
+     * @param torrentId 种子主主键
+     * @return 种子主
+     */
+    @Override
+    public BtTorrent selectBtTorrentByTorrentId(Long torrentId)
+    {
+        return btTorrentMapper.selectBtTorrentByTorrentId(torrentId);
+    }
+
+    /**
+     * 查询种子主列表
+     * 
+     * @param btTorrent 种子主
+     * @return 种子主
+     */
+    @Override
+    public List<BtTorrent> selectBtTorrentList(BtTorrent btTorrent)
+    {
+        return btTorrentMapper.selectBtTorrentList(btTorrent);
+    }
+
+    /**
+     * 新增种子主
+     * 
+     * @param btTorrent 种子主
+     * @return 结果
+     */
+    @Override
+    public int insertBtTorrent(BtTorrent btTorrent)
+    {
+        return btTorrentMapper.insertBtTorrent(btTorrent);
+    }
+
+    /**
+     * 修改种子主
+     * 
+     * @param btTorrent 种子主
+     * @return 结果
+     */
+    @Override
+    public int updateBtTorrent(BtTorrent btTorrent)
+    {
+        return btTorrentMapper.updateBtTorrent(btTorrent);
+    }
+
+    /**
+     * 批量删除种子主
+     * 
+     * @param torrentIds 需要删除的种子主主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentByTorrentIds(Long[] torrentIds)
+    {
+        return btTorrentMapper.deleteBtTorrentByTorrentIds(torrentIds);
+    }
+
+    /**
+     * 删除种子主信息
+     * 
+     * @param torrentId 种子主主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentByTorrentId(Long torrentId)
+    {
+        return btTorrentMapper.deleteBtTorrentByTorrentId(torrentId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentTagsServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentTagsServiceImpl.java
new file mode 100644
index 0000000..46fc50c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/service/impl/BtTorrentTagsServiceImpl.java
@@ -0,0 +1,93 @@
+package com.ruoyi.torrent.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.torrent.mapper.BtTorrentTagsMapper;
+import com.ruoyi.torrent.domain.BtTorrentTags;
+import com.ruoyi.torrent.service.IBtTorrentTagsService;
+
+/**
+ * 种子标签Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-21
+ */
+@Service
+public class BtTorrentTagsServiceImpl implements IBtTorrentTagsService 
+{
+    @Autowired
+    private BtTorrentTagsMapper btTorrentTagsMapper;
+
+    /**
+     * 查询种子标签
+     * 
+     * @param id 种子标签主键
+     * @return 种子标签
+     */
+    @Override
+    public BtTorrentTags selectBtTorrentTagsById(Long id)
+    {
+        return btTorrentTagsMapper.selectBtTorrentTagsById(id);
+    }
+
+    /**
+     * 查询种子标签列表
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 种子标签
+     */
+    @Override
+    public List<BtTorrentTags> selectBtTorrentTagsList(BtTorrentTags btTorrentTags)
+    {
+        return btTorrentTagsMapper.selectBtTorrentTagsList(btTorrentTags);
+    }
+
+    /**
+     * 新增种子标签
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 结果
+     */
+    @Override
+    public int insertBtTorrentTags(BtTorrentTags btTorrentTags)
+    {
+        return btTorrentTagsMapper.insertBtTorrentTags(btTorrentTags);
+    }
+
+    /**
+     * 修改种子标签
+     * 
+     * @param btTorrentTags 种子标签
+     * @return 结果
+     */
+    @Override
+    public int updateBtTorrentTags(BtTorrentTags btTorrentTags)
+    {
+        return btTorrentTagsMapper.updateBtTorrentTags(btTorrentTags);
+    }
+
+    /**
+     * 批量删除种子标签
+     * 
+     * @param ids 需要删除的种子标签主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentTagsByIds(Long[] ids)
+    {
+        return btTorrentTagsMapper.deleteBtTorrentTagsByIds(ids);
+    }
+
+    /**
+     * 删除种子标签信息
+     * 
+     * @param id 种子标签主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBtTorrentTagsById(Long id)
+    {
+        return btTorrentTagsMapper.deleteBtTorrentTagsById(id);
+    }
+}
diff --git a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentAnnounceMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentAnnounceMapper.xml
new file mode 100644
index 0000000..8b78641
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentAnnounceMapper.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.torrent.mapper.BtTorrentAnnounceMapper">
+    
+    <resultMap type="BtTorrentAnnounce" id="BtTorrentAnnounceResult">
+        <result property="id"    column="id"    />
+        <result property="torrentId"    column="torrent_id"    />
+        <result property="announceUrl"    column="announce_url"    />
+    </resultMap>
+
+    <sql id="selectBtTorrentAnnounceVo">
+        select id, torrent_id, announce_url from bt_torrent_announce
+    </sql>
+
+    <select id="selectBtTorrentAnnounceList" parameterType="BtTorrentAnnounce" resultMap="BtTorrentAnnounceResult">
+        <include refid="selectBtTorrentAnnounceVo"/>
+        <where>  
+            <if test="torrentId != null "> and torrent_id = #{torrentId}</if>
+            <if test="announceUrl != null  and announceUrl != ''"> and announce_url = #{announceUrl}</if>
+        </where>
+    </select>
+    
+    <select id="selectBtTorrentAnnounceById" parameterType="Long" resultMap="BtTorrentAnnounceResult">
+        <include refid="selectBtTorrentAnnounceVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBtTorrentAnnounce" parameterType="BtTorrentAnnounce" useGeneratedKeys="true" keyProperty="id">
+        insert into bt_torrent_announce
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="torrentId != null">torrent_id,</if>
+            <if test="announceUrl != null and announceUrl != ''">announce_url,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="torrentId != null">#{torrentId},</if>
+            <if test="announceUrl != null and announceUrl != ''">#{announceUrl},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBtTorrentAnnounce" parameterType="BtTorrentAnnounce">
+        update bt_torrent_announce
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="torrentId != null">torrent_id = #{torrentId},</if>
+            <if test="announceUrl != null and announceUrl != ''">announce_url = #{announceUrl},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBtTorrentAnnounceById" parameterType="Long">
+        delete from bt_torrent_announce where id = #{id}
+    </delete>
+
+    <delete id="deleteBtTorrentAnnounceByIds" parameterType="String">
+        delete from bt_torrent_announce where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentFileMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentFileMapper.xml
new file mode 100644
index 0000000..de8db04
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentFileMapper.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.torrent.mapper.BtTorrentFileMapper">
+    
+    <resultMap type="BtTorrentFile" id="BtTorrentFileResult">
+        <result property="id"    column="id"    />
+        <result property="torrentId"    column="torrent_id"    />
+        <result property="filePath"    column="file_path"    />
+        <result property="fileSize"    column="file_size"    />
+    </resultMap>
+
+    <sql id="selectBtTorrentFileVo">
+        select id, torrent_id, file_path, file_size from bt_torrent_file
+    </sql>
+
+    <select id="selectBtTorrentFileList" parameterType="BtTorrentFile" resultMap="BtTorrentFileResult">
+        <include refid="selectBtTorrentFileVo"/>
+        <where>  
+            <if test="torrentId != null "> and torrent_id = #{torrentId}</if>
+            <if test="filePath != null  and filePath != ''"> and file_path = #{filePath}</if>
+            <if test="fileSize != null "> and file_size = #{fileSize}</if>
+        </where>
+    </select>
+    
+    <select id="selectBtTorrentFileById" parameterType="Long" resultMap="BtTorrentFileResult">
+        <include refid="selectBtTorrentFileVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBtTorrentFile" parameterType="BtTorrentFile" useGeneratedKeys="true" keyProperty="id">
+        insert into bt_torrent_file
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="torrentId != null">torrent_id,</if>
+            <if test="filePath != null and filePath != ''">file_path,</if>
+            <if test="fileSize != null">file_size,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="torrentId != null">#{torrentId},</if>
+            <if test="filePath != null and filePath != ''">#{filePath},</if>
+            <if test="fileSize != null">#{fileSize},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBtTorrentFile" parameterType="BtTorrentFile">
+        update bt_torrent_file
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="torrentId != null">torrent_id = #{torrentId},</if>
+            <if test="filePath != null and filePath != ''">file_path = #{filePath},</if>
+            <if test="fileSize != null">file_size = #{fileSize},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBtTorrentFileById" parameterType="Long">
+        delete from bt_torrent_file where id = #{id}
+    </delete>
+
+    <delete id="deleteBtTorrentFileByIds" parameterType="String">
+        delete from bt_torrent_file where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
new file mode 100644
index 0000000..afb29d7
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.torrent.mapper.BtTorrentMapper">
+    
+    <resultMap type="BtTorrent" id="BtTorrentResult">
+        <result property="torrentId"    column="torrent_id"    />
+        <result property="infoHash"    column="info_hash"    />
+        <result property="name"    column="name"    />
+        <result property="length"    column="length"    />
+        <result property="pieceLength"    column="piece_length"    />
+        <result property="piecesCount"    column="pieces_count"    />
+        <result property="createdBy"    column="created_by"    />
+        <result property="torrentCreateTime"    column="torrent_create_time"    />
+        <result property="uploaderId"    column="uploader_id"    />
+        <result property="uploadTime"    column="upload_time"    />
+        <result property="filePath"    column="file_path"    />
+    </resultMap>
+
+    <sql id="selectBtTorrentVo">
+        select torrent_id, info_hash, name, length, piece_length, pieces_count, created_by, torrent_create_time, uploader_id, upload_time, file_path from bt_torrent
+    </sql>
+
+    <select id="selectBtTorrentList" parameterType="BtTorrent" resultMap="BtTorrentResult">
+        <include refid="selectBtTorrentVo"/>
+        <where>  
+            <if test="infoHash != null  and infoHash != ''"> and info_hash = #{infoHash}</if>
+            <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
+            <if test="length != null "> and length = #{length}</if>
+            <if test="pieceLength != null "> and piece_length = #{pieceLength}</if>
+            <if test="piecesCount != null "> and pieces_count = #{piecesCount}</if>
+            <if test="createdBy != null  and createdBy != ''"> and created_by = #{createdBy}</if>
+            <if test="torrentCreateTime != null "> and torrent_create_time = #{torrentCreateTime}</if>
+            <if test="uploaderId != null "> and uploader_id = #{uploaderId}</if>
+            <if test="uploadTime != null "> and upload_time = #{uploadTime}</if>
+            <if test="filePath != null  and filePath != ''"> and file_path = #{filePath}</if>
+        </where>
+    </select>
+    
+    <select id="selectBtTorrentByTorrentId" parameterType="Long" resultMap="BtTorrentResult">
+        <include refid="selectBtTorrentVo"/>
+        where torrent_id = #{torrentId}
+    </select>
+
+    <insert id="insertBtTorrent" parameterType="BtTorrent" useGeneratedKeys="true" keyProperty="torrentId">
+        insert into bt_torrent
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="infoHash != null and infoHash != ''">info_hash,</if>
+            <if test="name != null and name != ''">name,</if>
+            <if test="length != null">length,</if>
+            <if test="pieceLength != null">piece_length,</if>
+            <if test="piecesCount != null">pieces_count,</if>
+            <if test="createdBy != null">created_by,</if>
+            <if test="torrentCreateTime != null">torrent_create_time,</if>
+            <if test="uploaderId != null">uploader_id,</if>
+            <if test="uploadTime != null">upload_time,</if>
+            <if test="filePath != null">file_path,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="infoHash != null and infoHash != ''">#{infoHash},</if>
+            <if test="name != null and name != ''">#{name},</if>
+            <if test="length != null">#{length},</if>
+            <if test="pieceLength != null">#{pieceLength},</if>
+            <if test="piecesCount != null">#{piecesCount},</if>
+            <if test="createdBy != null">#{createdBy},</if>
+            <if test="torrentCreateTime != null">#{torrentCreateTime},</if>
+            <if test="uploaderId != null">#{uploaderId},</if>
+            <if test="uploadTime != null">#{uploadTime},</if>
+            <if test="filePath != null">#{filePath},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBtTorrent" parameterType="BtTorrent">
+        update bt_torrent
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="infoHash != null and infoHash != ''">info_hash = #{infoHash},</if>
+            <if test="name != null and name != ''">name = #{name},</if>
+            <if test="length != null">length = #{length},</if>
+            <if test="pieceLength != null">piece_length = #{pieceLength},</if>
+            <if test="piecesCount != null">pieces_count = #{piecesCount},</if>
+            <if test="createdBy != null">created_by = #{createdBy},</if>
+            <if test="torrentCreateTime != null">torrent_create_time = #{torrentCreateTime},</if>
+            <if test="uploaderId != null">uploader_id = #{uploaderId},</if>
+            <if test="uploadTime != null">upload_time = #{uploadTime},</if>
+            <if test="filePath != null">file_path = #{filePath},</if>
+        </trim>
+        where torrent_id = #{torrentId}
+    </update>
+
+    <delete id="deleteBtTorrentByTorrentId" parameterType="Long">
+        delete from bt_torrent where torrent_id = #{torrentId}
+    </delete>
+
+    <delete id="deleteBtTorrentByTorrentIds" parameterType="String">
+        delete from bt_torrent where torrent_id in 
+        <foreach item="torrentId" collection="array" open="(" separator="," close=")">
+            #{torrentId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentTagsMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentTagsMapper.xml
new file mode 100644
index 0000000..e7b1e48
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentTagsMapper.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.torrent.mapper.BtTorrentTagsMapper">
+    
+    <resultMap type="BtTorrentTags" id="BtTorrentTagsResult">
+        <result property="id"    column="id"    />
+        <result property="torrentId"    column="torrent_id"    />
+        <result property="tag"    column="tag"    />
+    </resultMap>
+
+    <sql id="selectBtTorrentTagsVo">
+        select id, torrent_id, tag from bt_torrent_tags
+    </sql>
+
+    <select id="selectBtTorrentTagsList" parameterType="BtTorrentTags" resultMap="BtTorrentTagsResult">
+        <include refid="selectBtTorrentTagsVo"/>
+        <where>  
+            <if test="torrentId != null "> and torrent_id = #{torrentId}</if>
+            <if test="tag != null  and tag != ''"> and tag = #{tag}</if>
+        </where>
+    </select>
+    
+    <select id="selectBtTorrentTagsById" parameterType="Long" resultMap="BtTorrentTagsResult">
+        <include refid="selectBtTorrentTagsVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBtTorrentTags" parameterType="BtTorrentTags" useGeneratedKeys="true" keyProperty="id">
+        insert into bt_torrent_tags
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="torrentId != null">torrent_id,</if>
+            <if test="tag != null and tag != ''">tag,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="torrentId != null">#{torrentId},</if>
+            <if test="tag != null and tag != ''">#{tag},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBtTorrentTags" parameterType="BtTorrentTags">
+        update bt_torrent_tags
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="torrentId != null">torrent_id = #{torrentId},</if>
+            <if test="tag != null and tag != ''">tag = #{tag},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBtTorrentTagsById" parameterType="Long">
+        delete from bt_torrent_tags where id = #{id}
+    </delete>
+
+    <delete id="deleteBtTorrentTagsByIds" parameterType="String">
+        delete from bt_torrent_tags where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/sql/announceMenu.sql b/sql/announceMenu.sql
new file mode 100644
index 0000000..49e9509
--- /dev/null
+++ b/sql/announceMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子的 Tracker 列', '3', '1', 'announce', 'system/announce/index', 1, 0, 'C', '0', '0', 'system:announce:list', '#', 'admin', sysdate(), '', null, '种子的 Tracker 列菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子的 Tracker 列查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:announce:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子的 Tracker 列新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:announce:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子的 Tracker 列修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:announce:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子的 Tracker 列删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:announce:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子的 Tracker 列导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:announce:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/sql/fileMenu.sql b/sql/fileMenu.sql
new file mode 100644
index 0000000..d4ba489
--- /dev/null
+++ b/sql/fileMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子包含的文件列', '3', '1', 'file', 'system/file/index', 1, 0, 'C', '0', '0', 'system:file:list', '#', 'admin', sysdate(), '', null, '种子包含的文件列菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子包含的文件列查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:file:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子包含的文件列新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:file:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子包含的文件列修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:file:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子包含的文件列删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:file:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子包含的文件列导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:file:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/sql/tagsMenu.sql b/sql/tagsMenu.sql
new file mode 100644
index 0000000..e8aa005
--- /dev/null
+++ b/sql/tagsMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子标签', '3', '1', 'tags', 'system/tags/index', 1, 0, 'C', '0', '0', 'system:tags:list', '#', 'admin', sysdate(), '', null, '种子标签菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子标签查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:tags:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子标签新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:tags:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子标签修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:tags:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子标签删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:tags:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子标签导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:tags:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/sql/torrentMenu.sql b/sql/torrentMenu.sql
new file mode 100644
index 0000000..9bcf460
--- /dev/null
+++ b/sql/torrentMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子主', '3', '1', 'torrent', 'system/torrent/index', 1, 0, 'C', '0', '0', 'system:torrent:list', '#', 'admin', sysdate(), '', null, '种子主菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子主查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:torrent:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子主新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:torrent:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子主修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:torrent:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子主删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:torrent:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('种子主导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:torrent:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file