Merge "增加了悬赏,标签查看,评论页面,标签上传后端有问题,评论还没跟后端连,优化了一些小界面"
diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts
index 919ae3b..92f45b3 100644
--- a/react-ui/config/routes.ts
+++ b/react-ui/config/routes.ts
@@ -75,6 +75,10 @@
   },
 
   {
+    path: '/torrent/comments/:torrentId',
+    component: './Torrent/Comments',
+  },
+  {
     name: 'tool',
     path: '/tool',
     routes: [
diff --git a/react-ui/src/pages/Reward/components/UpdateForm.tsx b/react-ui/src/pages/Reward/components/UpdateForm.tsx
new file mode 100644
index 0000000..526b946
--- /dev/null
+++ b/react-ui/src/pages/Reward/components/UpdateForm.tsx
@@ -0,0 +1,122 @@
+import { Modal, Form, Input, InputNumber, Select } from 'antd';
+import React from 'react';
+import { useIntl } from 'umi';
+import { RewardItem } from '../data';
+export type FormValueType = {
+    rewardId?: number;
+    title?: string;
+    description?: string;
+    amount?: number;
+    status?: string;
+    remark?: string;
+} & Partial<RewardItem>;
+
+export type UpdateFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    values: Partial<RewardItem>;
+    statusOptions: any;
+    readOnly?: boolean;
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    const { statusOptions } = props;
+
+    const intl = useIntl();
+
+    React.useEffect(() => {
+        form.resetFields();
+        form.setFieldsValue({
+            rewardId: props.values.rewardId,
+            title: props.values.title,
+            description: props.values.description,
+            amount: props.values.amount,
+            status: props.values.status,
+            remark: props.values.remark,
+        });
+    }, [form, props]);
+
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+        form.resetFields();
+    };
+    const handleFinish = async (values: FormValueType) => {
+        props.onSubmit(values);
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={props.readOnly ? '查看悬赏' : (props.values.rewardId ? '编辑悬赏' : '新增悬赏')}
+            open={props.open}
+            onOk={handleOk}
+            footer={props.readOnly ? null : undefined}
+            onCancel={handleCancel}
+        >
+            <Form
+                form={form}
+                onFinish={handleFinish}
+                initialValues={props.values}
+                labelCol={{ span: 6 }}
+                wrapperCol={{ span: 18 }}
+            >
+                <Form.Item name="rewardId" hidden={true}>
+                    <Input />
+                </Form.Item>
+                <Form.Item
+                    name="title"
+                    label="悬赏标题"
+                    rules={[{ required: true, message: '请输入悬赏标题!' }]}
+                >
+                    <Input placeholder="请输入悬赏标题" disabled={props.readOnly} />
+                </Form.Item>
+                <Form.Item
+                    name="description"
+                    label="悬赏描述"
+                    rules={[{ required: true, message: '请输入悬赏描述!' }]}
+                >
+                    <Input.TextArea rows={4} placeholder="请输入悬赏描述" disabled={props.readOnly} />
+                </Form.Item>
+                <Form.Item
+                    name="amount"
+                    label="悬赏金额"
+                    rules={[{ required: true, message: '请输入悬赏金额!' }]}
+                >
+                    <InputNumber
+                        style={{ width: '100%' }}
+                        min={0}
+                        precision={2}
+                        placeholder="请输入悬赏金额"
+                        disabled={props.readOnly}
+                    />
+                </Form.Item>
+                {/* <Form.Item
+                    name="status"
+                    label="悬赏状态"
+                >
+                    <Select
+                        placeholder="请选择悬赏状态"
+                        options={statusOptions.map((item: any) => ({
+                            value: item.dictValue,
+                            label: item.dictLabel,
+                        }))}
+                    />
+                </Form.Item> */}
+                <Form.Item
+                    name="remark"
+                    label="备注"
+                >
+                    <Input.TextArea rows={2} placeholder="请输入备注" disabled={props.readOnly} />
+                </Form.Item>
+            </Form>
+        </Modal>
+    );
+};
+
+export default UpdateForm;
\ No newline at end of file
diff --git a/react-ui/src/pages/Reward/data.d.ts b/react-ui/src/pages/Reward/data.d.ts
new file mode 100644
index 0000000..6dbec7b
--- /dev/null
+++ b/react-ui/src/pages/Reward/data.d.ts
@@ -0,0 +1,38 @@
+export interface RewardItem {
+    /** 悬赏ID */
+    rewardId?: number;
+    /** 悬赏标题 */
+    title: string;
+    /** 悬赏描述 */
+    description: string;
+    /** 悬赏金额 */
+    amount: number;
+    /** 悬赏状态(0进行中 1已完成 2已取消) */
+    status?: string;
+    /** 发布者ID */
+    publisherId?: number;
+    /** 接受者ID */
+    accepterId?: number;
+    /** 创建时间 */
+    createTime?: string;
+    /** 更新时间 */
+    updateTime?: string;
+    /** 备注 */
+    remark?: string;
+}
+
+export interface RewardListParams {
+    /** 当前的页码 */
+    pageNum?: number;
+    /** 页面的容量 */
+    pageSize?: number;
+    /** 悬赏标题 */
+    title?: string;
+    /** 悬赏状态 */
+    status?: string;
+    /** 开始时间 */
+    'params[beginTime]'?: string;
+    /** 结束时间 */
+    'params[endTime]'?: string;
+}
+
diff --git a/react-ui/src/pages/Reward/index.tsx b/react-ui/src/pages/Reward/index.tsx
new file mode 100644
index 0000000..231415c
--- /dev/null
+++ b/react-ui/src/pages/Reward/index.tsx
@@ -0,0 +1,338 @@
+import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Button, message, Modal, Switch } from 'antd';
+import React, { useRef, useState, useEffect } from 'react';
+import { FormattedMessage, useIntl } from 'umi';
+import { FooterToolbar, PageContainer } from '@ant-design/pro-layout';
+import type { ActionType, ProColumns } from '@ant-design/pro-table';
+import ProTable from '@ant-design/pro-table';
+import type { FormInstance } from 'antd';
+import { getRewardList, removeReward, updateReward, addReward } from './service';
+import UpdateForm from './components/UpdateForm';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+import { useAccess } from 'umi';
+import { RewardItem, RewardListParams } from './data';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: RewardItem[]) => {
+    const hide = message.loading('正在删除');
+    if (!selectedRows) return true;
+    try {
+        await removeReward(selectedRows.map((row) => row.rewardId).join(','));
+        hide();
+        message.success('删除成功');
+        return true;
+    } catch (error) {
+        hide();
+        message.error('删除失败,请重试');
+        return false;
+    }
+};
+
+const handleUpdate = async (fields: RewardItem) => {
+    const hide = message.loading('正在更新');
+    try {
+        const resp = await updateReward(fields);
+        hide();
+        if (resp.code === 200) {
+            message.success('更新成功');
+        } else {
+            message.error(resp.msg);
+        }
+        return true;
+    } catch (error) {
+        hide();
+        message.error('配置失败请重试!');
+        return false;
+    }
+};
+
+const handleAdd = async (fields: RewardItem) => {
+    const hide = message.loading('正在添加');
+    try {
+        const resp = await addReward(fields);
+        hide();
+        if (resp.code === 200) {
+            message.success('添加成功');
+        } else {
+            message.error(resp.msg);
+        }
+        return true;
+    } catch (error) {
+        hide();
+        message.error('配置失败请重试!');
+        return false;
+    }
+};
+
+const RewardTableList: React.FC = () => {
+    const formTableRef = useRef<FormInstance>();
+
+    const [modalVisible, setModalVisible] = useState<boolean>(false);
+    const [readOnly, setReadOnly] = useState<boolean>(false);
+
+    const actionRef = useRef<ActionType>();
+    const [currentRow, setCurrentRow] = useState<RewardItem>();
+    const [selectedRows, setSelectedRows] = useState<RewardItem[]>([]);
+
+    const [statusOptions, setStatusOptions] = useState<any>([]);
+
+    const access = useAccess();
+
+    /** 国际化配置 */
+    const intl = useIntl();
+
+    useEffect(() => {
+        getDictValueEnum('reward_status').then((data) => {
+            setStatusOptions(data);
+        });
+    }, []);
+
+    const columns: ProColumns<RewardItem>[] = [
+        {
+            title: '悬赏ID',
+            dataIndex: 'rewardId',
+            valueType: 'text',
+            hideInSearch: true,
+        },
+        {
+            title: '悬赏标题',
+            dataIndex: 'title',
+            valueType: 'text',
+        },
+        {
+            title: '悬赏金额',
+            dataIndex: 'amount',
+            valueType: 'money',
+            hideInSearch: true,
+        },
+        // {
+        //     title: '悬赏状态',
+        //     dataIndex: 'status',
+        //     valueType: 'select',
+        //     valueEnum: statusOptions,
+        //     render: (_, record) => {
+        //         return (<DictTag enums={statusOptions} value={record.status} />);
+        //     },
+        // },
+        {
+            title: '发布时间',
+            dataIndex: 'createTime',
+            valueType: 'dateRange',
+            render: (_, record) => {
+                return (<span>{record.createTime?.toString()}</span>);
+            },
+            search: {
+                transform: (value) => {
+                    return {
+                        'params[beginTime]': value[0],
+                        'params[endTime]': value[1],
+                    };
+                },
+            },
+        },
+        {
+            title: '备注',
+            dataIndex: 'remark',
+            valueType: 'text',
+            hideInSearch: true,
+        },
+        {
+            title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+            dataIndex: 'option',
+            width: '220px',
+            valueType: 'option',
+            render: (_, record) => [
+                <Button
+                    type="link"
+                    size="small"
+                    key="view"
+                    onClick={() => {
+                        setModalVisible(true);
+                        setCurrentRow(record);
+                        // 设置只读模式
+                        setReadOnly(true);
+                    }}
+                >
+                    查看
+                </Button>,
+                <Button
+                    type="link"
+                    size="small"
+                    key="edit"
+                    hidden={!access.hasPerms('reward:reward:edit')}
+                    onClick={() => {
+                        setModalVisible(true);
+                        setCurrentRow(record);
+                        setReadOnly(false);
+                    }}
+                >
+                    编辑
+                </Button>,
+                <Button
+                    type="link"
+                    size="small"
+                    danger
+                    key="batchRemove"
+                    hidden={!access.hasPerms('reward:reward:remove')}
+                    onClick={async () => {
+                        Modal.confirm({
+                            title: '删除',
+                            content: '确定删除该项吗?',
+                            okText: '确认',
+                            cancelText: '取消',
+                            onOk: async () => {
+                                const success = await handleRemove([record]);
+                                if (success) {
+                                    if (actionRef.current) {
+                                        actionRef.current.reload();
+                                    }
+                                }
+                            },
+                        });
+                    }}
+                >
+                    删除
+                </Button>,
+            ],
+        },
+    ];
+
+    return (
+        <PageContainer>
+            <div style={{ width: '100%', float: 'right' }}>
+                <ProTable<RewardItem>
+                    headerTitle={intl.formatMessage({
+                        id: 'pages.searchTable.title',
+                        defaultMessage: '信息',
+                    })}
+                    actionRef={actionRef}
+                    formRef={formTableRef}
+                    rowKey="rewardId"
+                    key="rewardList"
+                    search={{
+                        labelWidth: 120,
+                    }}
+                    toolBarRender={() => [
+                        <Button
+                            type="primary"
+                            key="add"
+                            hidden={!access.hasPerms('reward:reward:add')}
+                            onClick={async () => {
+                                setCurrentRow(undefined);
+                                setModalVisible(true);
+                            }}
+                        >
+                            <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+                        </Button>,
+                        <Button
+                            type="primary"
+                            key="remove"
+                            danger
+                            hidden={selectedRows?.length === 0 || !access.hasPerms('reward:reward:remove')}
+                            onClick={async () => {
+                                Modal.confirm({
+                                    title: '是否确认删除所选数据项?',
+                                    icon: <ExclamationCircleOutlined />,
+                                    content: '请谨慎操作',
+                                    async onOk() {
+                                        const success = await handleRemove(selectedRows);
+                                        if (success) {
+                                            setSelectedRows([]);
+                                            actionRef.current?.reloadAndRest?.();
+                                        }
+                                    },
+                                    onCancel() { },
+                                });
+                            }}
+                        >
+                            <DeleteOutlined />
+                            <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+                        </Button>,
+                    ]}
+                    request={(params) =>
+                        getRewardList({ ...params } as RewardListParams).then((res) => {
+                            return {
+                                data: res.rows,
+                                total: res.total,
+                                success: true,
+                            };
+                        })
+                    }
+                    columns={columns}
+                    rowSelection={{
+                        onChange: (_, selectedRows) => {
+                            setSelectedRows(selectedRows);
+                        },
+                    }}
+                />
+            </div>
+            {selectedRows?.length > 0 && (
+                <FooterToolbar
+                    extra={
+                        <div>
+                            <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+                            <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+                            <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+                        </div>
+                    }
+                >
+                    <Button
+                        key="remove"
+                        danger
+                        hidden={!access.hasPerms('reward:reward:remove')}
+                        onClick={async () => {
+                            Modal.confirm({
+                                title: '删除',
+                                content: '确定删除该项吗?',
+                                okText: '确认',
+                                cancelText: '取消',
+                                onOk: async () => {
+                                    const success = await handleRemove(selectedRows);
+                                    if (success) {
+                                        setSelectedRows([]);
+                                        actionRef.current?.reloadAndRest?.();
+                                    }
+                                },
+                            });
+                        }}
+                    >
+                        <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+                    </Button>
+                </FooterToolbar>
+            )}
+            <UpdateForm
+                readOnly={readOnly}
+                onSubmit={async (values) => {
+                    let success = false;
+                    if (values.rewardId) {
+                        success = await handleUpdate({ ...values } as RewardItem);
+                    } else {
+                        success = await handleAdd({ ...values } as RewardItem);
+                    }
+                    if (success) {
+                        setModalVisible(false);
+                        setCurrentRow(undefined);
+                        if (actionRef.current) {
+                            actionRef.current.reload();
+                        }
+                    }
+                }}
+                onCancel={() => {
+                    setModalVisible(false);
+                    setCurrentRow(undefined);
+                    setReadOnly(false);
+                }}
+                open={modalVisible}
+                values={currentRow || {}}
+                statusOptions={statusOptions}
+            />
+        </PageContainer>
+    );
+};
+
+export default RewardTableList;
\ No newline at end of file
diff --git a/react-ui/src/pages/Reward/service.ts b/react-ui/src/pages/Reward/service.ts
new file mode 100644
index 0000000..7550cf9
--- /dev/null
+++ b/react-ui/src/pages/Reward/service.ts
@@ -0,0 +1,49 @@
+import { request } from '@umijs/max';
+import type {
+    RewardItem,
+    RewardListParams,
+} from '@/pages/Reward/data'; // 假设你把 data.d.ts 放这里
+
+/** 获取悬赏任务列表 */
+export async function getRewardList(params?: RewardListParams) {
+    const queryString = params
+        ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+        : '';
+    const response = await request(`/api/reward/list${queryString}`, {
+        method: 'get',
+    });
+    if (!response || response.length === 0) {
+        return [{ id: 1, name: '虚假任务1', description: '这是一个虚假的任务描述' }, { id: 2, name: '虚假任务2', description: '这是另一个虚假的任务描述' }];
+    }
+    return response;
+}
+
+/** 获取悬赏任务详细信息 */
+export async function getReward(rewardId: number) {
+    return request(`/api/reward/${rewardId}`, {
+        method: 'get',
+    });
+}
+
+/** 新增悬赏任务 */
+export async function addReward(params: RewardItem) {
+    return request('/api/reward', {
+        method: 'post',
+        data: params,
+    });
+}
+
+/** 修改悬赏任务 */
+export async function updateReward(params: RewardItem) {
+    return request('/api/reward', {
+        method: 'put',
+        data: params,
+    });
+}
+
+/** 删除悬赏任务 */
+export async function removeReward(ids: string) {
+    return request(`/api/reward/${ids}`, {
+        method: 'delete',
+    });
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/Comments/data.d.ts b/react-ui/src/pages/Torrent/Comments/data.d.ts
new file mode 100644
index 0000000..922cb29
--- /dev/null
+++ b/react-ui/src/pages/Torrent/Comments/data.d.ts
@@ -0,0 +1,15 @@
+/** 种子评论表 */
+export interface SysTorrentComment {
+    /** 评论ID */
+    commentId: number;
+    /** 种子ID */
+    torrentId: number;
+    /** 评论用户ID */
+    userId: number;
+    /** 评论内容 */
+    content: string;
+    /** 创建时间 */
+    createTime: Date;
+    /** 父评论ID,用于回复 */
+    parentId: number;
+}
diff --git a/react-ui/src/pages/Torrent/Comments/index.tsx b/react-ui/src/pages/Torrent/Comments/index.tsx
new file mode 100644
index 0000000..9a9f25a
--- /dev/null
+++ b/react-ui/src/pages/Torrent/Comments/index.tsx
@@ -0,0 +1,194 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Button, Card, Form, Input, List, message, Avatar } from 'antd';
+import { ArrowLeftOutlined } from '@ant-design/icons';
+import { Layout } from 'antd';
+
+const { Content } = Layout;
+const { TextArea } = Input;
+
+interface CommentItem {
+    id: number;
+    content: string;
+    createTime: string;
+    createBy: string;
+    torrentId: number;
+}
+
+const TorrentComments: React.FC = () => {
+    const { torrentId } = useParams<{ torrentId: string }>();
+    const navigate = useNavigate();
+    const [form] = Form.useForm();
+    const [comments, setComments] = useState<CommentItem[]>([]);
+    const [submitting, setSubmitting] = useState(false);
+
+    // 获取评论列表
+    const fetchComments = async () => {
+        try {
+            // 模拟评论数据
+            const mockComments: CommentItem[] = [
+                {
+                    id: 1,
+                    content: '这个种子非常好,下载速度很快!',
+                    createTime: '2024-01-15 14:30:00',
+                    createBy: '张三',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 2,
+                    content: '资源质量很高,感谢分享。',
+                    createTime: '2024-01-15 15:20:00',
+                    createBy: '李四',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 3,
+                    content: '这个版本很完整,推荐下载!',
+                    createTime: '2024-01-15 16:45:00',
+                    createBy: '王五',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 1,
+                    content: '这个种子非常好,下载速度很快!',
+                    createTime: '2024-01-15 14:30:00',
+                    createBy: '张三',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 2,
+                    content: '资源质量很高,感谢分享。',
+                    createTime: '2024-01-15 15:20:00',
+                    createBy: '李四',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 3,
+                    content: '这个版本很完整,推荐下载!',
+                    createTime: '2024-01-15 16:45:00',
+                    createBy: '王五',
+                    torrentId: Number(torrentId)
+                }
+            ];
+            setComments(mockComments);
+        } catch (error) {
+            message.error('获取评论失败');
+        }
+    };
+
+    useEffect(() => {
+        if (torrentId) {
+            fetchComments();
+        }
+    }, [torrentId]);
+
+    // 提交评论
+    const handleSubmit = async () => {
+        try {
+            const values = await form.validateFields();
+            setSubmitting(true);
+
+            // TODO: 替换为实际的API调用
+            // await addComment({
+            //   torrentId: Number(torrentId),
+            //   content: values.content,
+            // });
+
+            message.success('评论成功');
+            form.resetFields();
+            fetchComments(); // 刷新评论列表
+        } catch (error) {
+            message.error('评论失败');
+        } finally {
+            setSubmitting(false);
+        }
+    };
+
+    return (
+        <Content style={{
+            height: '100vh',
+            display: 'flex',
+            flexDirection: 'column',
+            overflow: 'hidden' // 防止内容溢出
+        }}>
+            {/* 顶部标题栏 */}
+            <div style={{
+                padding: '16px',
+                borderBottom: '1px solid #f0f0f0',
+                display: 'flex',
+                alignItems: 'center',
+                backgroundColor: '#fff',
+                zIndex: 10
+            }}>
+                <Button
+                    type="link"
+                    icon={<ArrowLeftOutlined />}
+                    onClick={() => navigate(-1)}
+                    style={{ marginRight: '10px', padding: 0 }}
+                />
+                <span style={{ fontSize: '16px', fontWeight: 'bold' }}>种子评论</span>
+            </div>
+
+            {/* 评论列表区域 - 可滚动 */}
+            <div style={{
+                flex: 1,
+                overflowY: 'auto',
+                padding: '0 16px',
+                paddingBottom: '16px'
+            }}>
+                <List
+                    className="comment-list"
+                    itemLayout="horizontal"
+                    dataSource={comments}
+                    renderItem={(item) => (
+                        <List.Item>
+                            <List.Item.Meta
+                                avatar={<Avatar>{item.createBy[0]}</Avatar>}
+                                title={item.createBy}
+                                description={
+                                    <div>
+                                        <div>{item.content}</div>
+                                        <div style={{ color: '#8c8c8c', fontSize: '12px', marginTop: '8px' }}>
+                                            {item.createTime}
+                                        </div>
+                                    </div>
+                                }
+                            />
+                        </List.Item>
+                    )}
+                />
+            </div>
+
+            {/* 评论输入框 - 固定在父组件底部 */}
+            <div style={{
+                position: 'relative',
+                padding: '16px',
+                backgroundColor: '#fff',
+                borderTop: '1px solid #f0f0f0',
+                boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.06)'
+            }}>
+                <Form form={form}>
+                    <Form.Item
+                        name="content"
+                        rules={[{ required: true, message: '请输入评论内容' }]}
+                        style={{ marginBottom: '12px' }}
+                    >
+                        <TextArea rows={3} placeholder="请输入您的评论" />
+                    </Form.Item>
+                    <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
+                        <Button
+                            htmlType="submit"
+                            loading={submitting}
+                            onClick={handleSubmit}
+                            type="primary"
+                        >
+                            提交评论
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </div>
+        </Content>
+    );
+};
+
+export default TorrentComments;
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/Comments/service.ts b/react-ui/src/pages/Torrent/Comments/service.ts
new file mode 100644
index 0000000..f4243b6
--- /dev/null
+++ b/react-ui/src/pages/Torrent/Comments/service.ts
@@ -0,0 +1,30 @@
+import { request } from '@umijs/max';
+import type { SysTorrentComment } from '@/pages/Torrent/Comments/data';
+
+/** 获取种子评论列表 */
+export async function listComments(torrentId: number) {
+    return request<SysTorrentComment[]>(`/api/system/torrent/comment/list`, {
+        method: 'get',
+        params: { torrentId },
+    });
+}
+
+/** 新增评论 */
+export async function addComment(data: {
+    torrentId: number;
+    userId: number;
+    content: string;
+    parentId?: number;
+}) {
+    return request(`/api/system/torrent/comment`, {
+        method: 'post',
+        data,
+    });
+}
+
+/** 删除评论 */
+export async function deleteComment(commentId: number) {
+    return request(`/api/system/torrent/comment/${commentId}`, {
+        method: 'delete',
+    });
+}
diff --git a/react-ui/src/pages/Torrent/index.tsx b/react-ui/src/pages/Torrent/index.tsx
index bb058ae..30b7ef3 100644
--- a/react-ui/src/pages/Torrent/index.tsx
+++ b/react-ui/src/pages/Torrent/index.tsx
@@ -1,5 +1,5 @@
-import React, { useRef, useState } from 'react';
-import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined } from '@ant-design/icons';
+import React, { useRef, useState, useEffect } from 'react';
+import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined, DownloadOutlined, CommentOutlined } from '@ant-design/icons';
 import {
   Button,
   Modal,
@@ -13,21 +13,29 @@
   Layout,
   Upload,
   UploadProps,
+  Select,
+  Tag, // 导入Tag组件用于显示标签
 } from 'antd';
 import { ProTable, ActionType, ProColumns, ProDescriptions, ProDescriptionsItemProps } from '@ant-design/pro-components';
-import type { BtTorrent } from './data';
+import { useNavigate } from 'react-router-dom';
+import type { BtTorrent, BtTorrentTag } from './data';
 import {
   listBtTorrent,
   getBtTorrent,
   addBtTorrent,
   updateBtTorrent,
   removeBtTorrent,
-  uploadTorrent, // Function to handle torrent upload
+  uploadTorrent,
+  downloadTorrent,
+  addBtTorrentTag,
+  listBtTorrentTags,
+  getBtTorrentTag
 } from './service';
 
 const { Content } = Layout;
 
 const BtTorrentPage: React.FC = () => {
+  const navigate = useNavigate();
   const actionRef = useRef<ActionType>();
   const [form] = Form.useForm();
   const [modalVisible, setModalVisible] = useState(false);
@@ -35,19 +43,38 @@
   const [current, setCurrent] = useState<Partial<BtTorrent>>({});
   const [uploadModalVisible, setUploadModalVisible] = useState(false); // State for upload modal
   const [uploadFile, setUploadFile] = useState<File | null>(null); // State to store selected file
+  const [uploadForm] = Form.useForm(); // Form for upload modal
+  const [torrentTags, setTorrentTags] = useState<BtTorrentTag[]>([]); // 修改为数组类型来存储多个标签
 
   // Columns for the ProTable (the table displaying torrents)
   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);
+            try {
+              // 获取详细信息
+              const res = await getBtTorrent(entity.torrentId!);
+              console.log('获取的种子详情:', res); // 调试用
+
+              // 确保res是对象类型并且包含数据
+              if (res && typeof res === 'object') {
+                // 如果API返回了data包装,则提取data
+                const torrentData = res.data ? res.data : res;
+                setCurrent(torrentData);
+
+                // 先设置当前种子,然后获取标签
+                await handleGetTags(entity.torrentId!);
+                setDrawerVisible(true);
+              } else {
+                message.error('获取种子详情格式错误');
+              }
+            } catch (error) {
+              console.error('获取种子详情出错:', error);
+              message.error('获取种子详情失败');
+            }
           }}
         >
           {dom}
@@ -55,36 +82,56 @@
       ),
     },
     { title: '名称', dataIndex: 'name' },
-    { title: 'infoHash', dataIndex: 'infoHash' },
+    // { 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: '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 key="view" type="link" icon={<EyeOutlined />} onClick={async () => {
+          try {
+            // 获取详细信息
+            const res = await getBtTorrent(record.torrentId!);
+            console.log('获取的种子详情:', res); // 调试用
+
+            // 确保res是对象类型并且包含数据
+            if (res && typeof res === 'object') {
+              // 如果API返回了data包装,则提取data
+              const torrentData = res.data ? res.data : res;
+              setCurrent(torrentData);
+
+              // 获取标签
+              await handleGetTags(record.torrentId!);
+              setDrawerVisible(true);
+            } else {
+              message.error('获取种子详情格式错误');
+            }
+          } catch (error) {
+            console.error('获取种子详情出错:', error);
+            message.error('获取详情失败');
+          }
         }}>查看</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>,
+        <Button key="download" type="link" icon={<DownloadOutlined />} onClick={async () => {
+          try {
+            const blob = await downloadTorrent(record.torrentId!);
+            const url = window.URL.createObjectURL(blob);
+            const link = document.createElement('a');
+            link.href = url;
+            link.download = `${record.name}.torrent`;
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            window.URL.revokeObjectURL(url);
+            message.success('下载成功');
+          } catch (error: any) {
+            message.error('下载失败');
+          }
+        }}>下载</Button>,
+
       ],
     },
   ];
@@ -115,16 +162,19 @@
         throw new Error('请选择一个文件');
       }
 
-      // Call the uploadTorrent function to upload the file
+      const values = await uploadForm.validateFields();
+      console.log(file);
+      // Call the uploadTorrent function to upload the file with additional info
       await uploadTorrent(file);
 
       // Show a success message
       message.success('文件上传成功');
 
-      // Close the upload modal
+      // Close the upload modal and reset form
       setUploadModalVisible(false);
+      uploadForm.resetFields();
 
-      // Optionally reload the table or perform other actions (e.g., refresh list)
+      // Reload the table
       actionRef.current?.reload();
 
     } catch (error) {
@@ -132,6 +182,64 @@
     }
   };
 
+  // 修改获取标签的函数,处理API特定的响应格式
+  const handleGetTags = async (id: number) => {
+    try {
+      // 根据API的响应格式,获取rows数组中的标签
+      const response = await listBtTorrentTags({ torrentId: id });
+      console.log('API标签响应:', response);
+
+      // 检查响应格式并提取rows数组
+      if (response && response.rows && Array.isArray(response.rows)) {
+        setTorrentTags(response.rows);
+        console.log('设置标签:', response.rows);
+      } else {
+        console.log('未找到标签或格式不符');
+        setTorrentTags([]);
+      }
+    } catch (error) {
+      console.error('获取标签失败:', error);
+      message.error('获取标签失败');
+      setTorrentTags([]);
+    }
+  };
+
+  useEffect(() => {
+    if (current?.torrentId) {
+      handleGetTags(current.torrentId);
+    } else {
+      setTorrentTags([]); // 清空标签当没有选中种子时
+    }
+  }, [current]);
+
+  // 渲染标签列表的函数
+  const renderTags = () => {
+    if (!torrentTags || torrentTags.length === 0) {
+      return <span style={{ color: '#999' }}>暂无标签</span>;
+    }
+
+    // 定义一些可能的标签颜色
+    const tagColors = ['blue', 'green', 'cyan', 'purple', 'magenta', 'orange', 'gold', 'lime'];
+
+    return (
+      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
+        {torrentTags.map((tag, index) => {
+          // 根据索引轮换颜色
+          const colorIndex = index % tagColors.length;
+          return (
+            <Tag
+              key={tag.id || tag.torrentId || index}
+              color={tagColors[colorIndex]}
+              style={{ margin: '0 4px 4px 0', padding: '2px 8px' }}
+            >
+              {tag.tag}
+            </Tag>
+          );
+        })}
+      </div>
+    );
+  };
+
   return (
     <Content>
       <Card bordered={false}>
@@ -142,22 +250,10 @@
           search={{ labelWidth: 100 }}
           toolBarRender={() => [
             <Button
-              key="add"
-              type="primary"
-              icon={<PlusOutlined />}
-              onClick={() => {
-                form.resetFields();
-                setCurrent({});
-                setModalVisible(true);
-              }}
-            >
-              新增
-            </Button>,
-            <Button
               key="upload"
               type="primary"
               icon={<UploadOutlined />}
-              onClick={() => setUploadModalVisible(true)} // Show the upload modal
+              onClick={() => setUploadModalVisible(true)}
             >
               上传种子文件
             </Button>
@@ -202,37 +298,155 @@
         {/* 上传种子文件的Modal */}
         <Modal
           title="上传种子文件"
-          visible={uploadModalVisible}
-          onCancel={() => setUploadModalVisible(false)}
-          footer={null}
+          open={uploadModalVisible}
+          onCancel={() => {
+            setUploadModalVisible(false);
+            uploadForm.resetFields();
+          }}
+          onOk={() => {
+            if (uploadFile) {
+              handleFileUpload(uploadFile);
+            } else {
+              message.error('请选择文件');
+            }
+          }}
         >
-          <Upload
-            customRequest={({ file, onSuccess, onError }) => {
-              setUploadFile(file);
-              handleFileUpload(file);
-              onSuccess?.();
-            }}
-            showUploadList={false}
-            accept=".torrent"
-          >
-            <Button icon={<UploadOutlined />}>点击上传 .torrent 文件</Button>
-          </Upload>
+          <Form form={uploadForm} layout="vertical">
+            <Form.Item
+              name="file"
+              label="种子文件"
+              rules={[{ required: true, message: '请选择种子文件' }]}
+            >
+              <Upload
+                customRequest={({ file, onSuccess }) => {
+                  setUploadFile(file as File);
+                  onSuccess?.();
+                }}
+                showUploadList={true}
+                maxCount={1}
+                accept=".torrent"
+                onRemove={() => setUploadFile(null)}
+              >
+                <Button icon={<UploadOutlined />}>选择 .torrent 文件</Button>
+              </Upload>
+            </Form.Item>
+            <Form.Item
+              name="description"
+              label="介绍"
+              rules={[{ required: true, message: '请输入种子介绍' }]}
+            >
+              <Input.TextArea rows={4} placeholder="请输入种子文件的详细介绍" />
+            </Form.Item>
+            <Form.Item
+              name="tags"
+              label="标签"
+              rules={[{ required: true, message: '请输入标签' }]}
+            >
+              <Select
+                mode="tags"
+                style={{ width: '100%' }}
+                placeholder="请输入标签,按回车键确认"
+                tokenSeparators={[',']}
+              />
+            </Form.Item>
+          </Form>
         </Modal>
 
         {/* 详情抽屉 */}
         <Drawer
-          width={500}
+          width={600}
           open={drawerVisible}
           onClose={() => setDrawerVisible(false)}
           title="种子详情"
+          extra={
+            <Button
+              type="primary"
+              icon={<CommentOutlined />}
+              onClick={() => navigate(`/torrent/comments/${current.torrentId}`)}
+            >
+              查看评论
+            </Button>
+          }
         >
           {current && (
-            <ProDescriptions<BtTorrent>
-              column={1}
-              title={current.name}
-              request={async () => ({ data: current })}
-              columns={columns as ProDescriptionsItemProps<BtTorrent>[]}
-            />
+            <>
+              {/* 不要使用request属性,直接使用dataSource */}
+              <ProDescriptions<BtTorrent>
+                column={1}
+                title={current.name}
+                dataSource={current}
+                columns={[
+                  {
+                    title: '种子ID',
+                    dataIndex: 'torrentId',
+                    valueType: 'text'
+                  },
+                  {
+                    title: '名称',
+                    dataIndex: 'name',
+                    valueType: 'text'
+                  },
+                  {
+                    title: 'infoHash',
+                    dataIndex: 'infoHash',
+                    valueType: 'text',
+                    copyable: true
+                  },
+                  {
+                    title: '大小 (bytes)',
+                    dataIndex: 'length',
+                    valueType: 'digit',
+
+                  },
+                  {
+                    title: '分片大小',
+                    dataIndex: 'pieceLength',
+                    valueType: 'digit'
+                  },
+                  {
+                    title: '片段数',
+                    dataIndex: 'piecesCount',
+                    valueType: 'digit'
+                  },
+                  {
+                    title: '创建人',
+                    dataIndex: 'createdBy',
+                    valueType: 'text'
+                  },
+                  {
+                    title: '上传时间',
+                    dataIndex: 'uploadTime',
+                    valueType: 'dateTime'
+                  },
+                  {
+                    title: '标签',
+                    dataIndex: 'tags',
+                    render: () => renderTags()
+                  }
+                ] as ProDescriptionsItemProps<BtTorrent>[]}
+              />
+
+              {/* 如果需要显示额外信息,可以在这里添加 */}
+              {current.description && (
+                <div style={{ marginTop: 16 }}>
+                  <h3>介绍</h3>
+                  <p>{current.description}</p>
+                </div>
+              )}
+
+              {/* 添加调试信息 - 开发时使用,生产环境可以移除 */}
+              {/* <div style={{ marginTop: 20, background: '#f5f5f5', padding: 10, borderRadius: 4 }}>
+                <h4>调试信息:</h4>
+                <p>当前种子ID: {current.torrentId}</p>
+                <p>标签数量: {torrentTags?.length || 0}</p>
+                <details>
+                  <summary>查看完整数据</summary>
+                  <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(current, null, 2)}</pre>
+                  <h5>标签数据:</h5>
+                  <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(torrentTags, null, 2)}</pre>
+                </details>
+              </div> */}
+            </>
           )}
         </Drawer>
       </Card>
@@ -240,4 +454,4 @@
   );
 };
 
-export default BtTorrentPage;
+export default BtTorrentPage;
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/service.ts b/react-ui/src/pages/Torrent/service.ts
index 8e15618..e526151 100644
--- a/react-ui/src/pages/Torrent/service.ts
+++ b/react-ui/src/pages/Torrent/service.ts
@@ -6,6 +6,14 @@
   BtTorrentTag,
 } from '@/pages/Torrent/data'; // 假设你把 data.d.ts 放这里
 
+/** 下载种子文件 */
+export async function downloadTorrent(torrentId: number) {
+  return request(`/api/system/torrent/download/${torrentId}`, {
+    method: 'get',
+    responseType: 'blob',
+  });
+}
+
 // ================================
 // 种子主表(bt_torrent)接口
 // ================================
@@ -62,6 +70,7 @@
 
 /** 获取文件详情 */
 export async function getBtTorrentFile(id: number) {
+
   return request<BtTorrentFile>(`/api/system/file/${id}`, {
     method: 'get',
   });
diff --git a/react-ui/src/pages/User/Center/index.tsx b/react-ui/src/pages/User/Center/index.tsx
index 2ce308c..7272fa3 100644
--- a/react-ui/src/pages/User/Center/index.tsx
+++ b/react-ui/src/pages/User/Center/index.tsx
@@ -14,6 +14,7 @@
 import AvatarCropper from './components/AvatarCropper';
 import { useRequest } from '@umijs/max';
 import { getUserInfo } from '@/services/session';
+import { getUserRateInfo } from '@/services/system/user';
 import { PageLoading } from '@ant-design/pro-components';
 
 const operationTabList = [
@@ -38,20 +39,25 @@
 export type tabKeyType = 'base' | 'password';
 
 const Center: React.FC = () => {
-  
+
   const [tabKey, setTabKey] = useState<tabKeyType>('base');
-  
+
   const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
-  
+
   //  获取用户信息
   const { data: userInfo, loading } = useRequest(async () => {
-    return { data: await getUserInfo()};
+    return { data: await getUserInfo() };
+  });
+
+  const { data: userRateInfo } = useRequest(async () => {
+    return { data: await getUserRateInfo() };
   });
   if (loading) {
     return <div>loading...</div>;
   }
 
-  const currentUser = userInfo?.user;
+  const currentUser = { ...userInfo?.user, ...userRateInfo?.data };
+
 
   //  渲染用户信息
   const renderUserInfo = ({
@@ -60,6 +66,10 @@
     email,
     sex,
     dept,
+    uploadCount,
+    downloadCount,
+    rateCount,
+    score,
   }: Partial<API.CurrentUser>) => {
     return (
       <List>
@@ -109,6 +119,39 @@
         </List.Item>
         <List.Item>
           <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            上传量
+          </div>
+          <div>{uploadCount}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            下载量
+          </div>
+          <div>{downloadCount}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            分享率
+          </div>
+          <div>{rateCount}</div>
+        </List.Item>
+        {/* <List.Item>
+          <div>
             <ClusterOutlined
               style={{
                 marginRight: 8,
@@ -117,6 +160,17 @@
             部门
           </div>
           <div>{dept?.deptName}</div>
+        </List.Item> */}
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            积分
+          </div>
+          <div>{score}</div>
         </List.Item>
       </List>
     );
@@ -147,13 +201,13 @@
             loading={loading}
           >
             {!loading && (
-              <div style={{ textAlign: "center"}}>
-                <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}>
+              <div style={{ textAlign: "center" }}>
+                <div className={styles.avatarHolder} onClick={() => { setCropperModalOpen(true) }}>
                   <img alt="" src={currentUser.avatar} />
                 </div>
                 {renderUserInfo(currentUser)}
                 <Divider dashed />
-                <div className={styles.team}>
+                {/* <div className={styles.team}>
                   <div className={styles.teamTitle}>角色</div>
                   <Row gutter={36}>
                     {currentUser.roles &&
@@ -168,7 +222,7 @@
                         </Col>
                       ))}
                   </Row>
-                </div>
+                </div> */}
               </div>
             )}
           </Card>
@@ -188,7 +242,7 @@
       </Row>
       <AvatarCropper
         onFinished={() => {
-          setCropperModalOpen(false);     
+          setCropperModalOpen(false);
         }}
         open={cropperModalOpen}
         data={currentUser.avatar}
diff --git a/react-ui/src/services/system/user.ts b/react-ui/src/services/system/user.ts
index da997bf..9f45f12 100644
--- a/react-ui/src/services/system/user.ts
+++ b/react-ui/src/services/system/user.ts
@@ -79,6 +79,14 @@
   })
 }
 
+// 查询用户上传,下载,分享率等信息
+export function getUserRateInfo() {
+  return request('/api/system/user/profile/info', {
+    method: 'get'
+  })
+}
+
+
 export function updateUserProfile(data: API.CurrentUser) {
   return request<API.Result>('/api/system/user/profile', {
     method: 'put',
diff --git a/react-ui/src/services/typings.d.ts b/react-ui/src/services/typings.d.ts
index 1e0b8b5..20f44dc 100644
--- a/react-ui/src/services/typings.d.ts
+++ b/react-ui/src/services/typings.d.ts
@@ -57,6 +57,10 @@
     dept?: Dept;
     roles?: Role[];
     permissions: string[];
+    uploadCount?: number;
+    downloadCount?: number;
+    rateCount?: number;
+    score?: number;
   }
 
   interface UserInfoVO {
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java
index 8fbb688..f3bd624 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java
@@ -1,10 +1,7 @@
 package com.ruoyi.announce.controller;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.announce.service.IAnnounceService;
-import org.apache.commons.codec.DecoderException;
-import org.apache.commons.codec.net.URLCodec;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -13,116 +10,52 @@
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
-import java.util.Base64;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @RestController
 public class AnnounceController extends BaseController {
-    // 在你的 Controller 中:
 
     @Autowired
     private IAnnounceService announceService;
 
-
-    private byte[] decodeInfoHashAndPeerId(String param) {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        for (int i = 0; i < param.length(); ) {
-            char c = param.charAt(i);
-            if (c == '%' && i + 2 < param.length()) {
-                char hi = param.charAt(i + 1);
-                char lo = param.charAt(i + 2);
-                if (isHexDigit(hi) && isHexDigit(lo)) {
-                    int value = Character.digit(hi, 16) << 4 | Character.digit(lo, 16);
-                    bos.write(value);
-                    i += 3;
-                    continue;
-                }
-            }
-            // 不是合法的 %xx,直接跳过或处理为异常字符
-            bos.write((byte) c); // 或者 throw new IllegalArgumentException(...)
-            i++;
-        }
-        return bos.toByteArray();
-    }
-
-    private boolean isHexDigit(char c) {
-        return (c >= '0' && c <= '9') ||
-                (c >= 'A' && c <= 'F') ||
-                (c >= 'a' && c <= 'f');
-    }
-
-
     /**
      * BT Tracker /announce 接口
      * 接收客户端(qBittorrent 等)发来的 announce 请求,返回 bencoded peers 列表
      */
-
     @GetMapping(value = "/announce", produces = "application/x-bittorrent")
-    public void announce(HttpServletRequest request, HttpServletResponse response) throws Exception {
+    public void announce(
+            HttpServletRequest request,
+            HttpServletResponse response,
+            @RequestParam("info_hash") String infoHashParam,
+            @RequestParam("peer_id")   String peerIdParam,
+            @RequestParam("port")      int port,
+            @RequestParam("uploaded")  long uploaded,
+            @RequestParam("downloaded") long downloaded,
+            @RequestParam("left")      long left,
+            @RequestParam(value = "event",    required = false) String event,
+            @RequestParam("passkey")   String passkey
+    ) throws Exception {
+        // 1. URL Decode 得到原始二进制
+        byte[] infoHash = URLDecoder.decode(infoHashParam, StandardCharsets.ISO_8859_1.name())
+                .getBytes(StandardCharsets.ISO_8859_1);
+        byte[] peerId   = URLDecoder.decode(peerIdParam,   StandardCharsets.ISO_8859_1.name())
+                .getBytes(StandardCharsets.ISO_8859_1);
 
-        // —— 4. 获取参数(不使用 @RequestParam) ——
-        String infoHashParam = request.getParameter("info_hash");
-        String peerIdParam   = request.getParameter("peer_id");
-        String portStr       = request.getParameter("port");
-        String uploadedStr   = request.getParameter("uploaded");
-        String downloadedStr = request.getParameter("downloaded");
-        String leftStr       = request.getParameter("left");
-        String event         = request.getParameter("event");
-        String passkey       = request.getParameter("passkey");
-        // 打印接收到的参数
-        System.out.println("Received announce request:");
-        System.out.println("  info_hash: " + infoHashParam);
-        System.out.println("  peer_id:   " + peerIdParam);
-        System.out.println("  port:      " + portStr);
-        System.out.println("  uploaded:  " + uploadedStr);
-        System.out.println("  downloaded:" + downloadedStr);
-        System.out.println("  left:      " + leftStr);
-        System.out.println("  event:     " + event);
-        System.out.println("  passkey:   " + passkey);
-        System.out.println("  IP:        " + request.getRemoteAddr());
-        // —— 校验是否有必要参数为空 ——
-        if (infoHashParam == null || peerIdParam == null || portStr == null ||
-                uploadedStr == null || downloadedStr == null || leftStr == null) {
-            System.out.println("参数缺失,终止处理。");
-            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-            response.setContentType("text/plain");
-            response.getWriter().write("Missing required announce parameters.");
-            return;
-        }
-
-
-
-        // —— 5. 转换参数类型 ——
-        int port = Integer.parseInt(portStr);
-        long uploaded = Long.parseLong(uploadedStr);
-        long downloaded = Long.parseLong(downloadedStr);
-        long left = Long.parseLong(leftStr);
-
-        // 确保 URL 解码不会失败,使用更健壮的方法
-        byte[] infoHash = decodeInfoHashAndPeerId(infoHashParam);
-        byte[] peerId   = decodeInfoHashAndPeerId(peerIdParam);
-
-        // —— 7. 调用业务逻辑处理 ——
+        // 2. 处理 announce 请求(验证 passkey,更新 peer 列表,获取 peers 信息)
         Map<String, Object> reply = announceService.handleAnnounce(
                 infoHash, peerId, port, uploaded, downloaded, left, event, passkey,
                 request.getRemoteAddr()
         );
 
-        // —— 8. 返回 Bencode 编码的 tracker 响应 ——
+        // 3. bencode 编码并返回给客户端
         response.setStatus(HttpServletResponse.SC_OK);
         response.setHeader("Content-Type", "application/x-bittorrent");
         try (var out = response.getOutputStream()) {
             byte[] bencoded = announceService.encodeBencode(reply);
-            System.out.println(bencoded);
-
             out.write(bencoded);
             out.flush();
         }
     }
-
 }
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java
index 4408fa1..f80050f 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java
@@ -6,7 +6,6 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
@@ -36,72 +35,46 @@
     ) throws Exception {
         // 1. 转 hex 作为 Redis key 前缀
         String infoHashHex = bytesToHex(infoHash);
-        String peerIdStr = new String(peerId, StandardCharsets.ISO_8859_1);
+        String peerIdStr   = new String(peerId, StandardCharsets.ISO_8859_1);
 
         // 2. 校验 passkey(可根据业务到 MySQL 查 userId,此处略过)
 
-        // 3. 在 Redis 中记录/刷新此 peer 的信息(TTL 120 秒)
+        // 3. 在 Redis 中记录/刷新此 peer 的信息
+        //    使用 Hash 存储详情,TTL 120 秒
         String peerKey = "peer:" + infoHashHex + ":" + peerIdStr;
         Map<String, Object> peerData = new HashMap<>();
-        peerData.put("ip", ip);
-        peerData.put("port", port);
-        peerData.put("uploaded", uploaded);
+        peerData.put("ip",         ip);
+        peerData.put("port",       port);
+        peerData.put("uploaded",   uploaded);
         peerData.put("downloaded", downloaded);
-        peerData.put("left", left);
-        peerData.put("lastSeen", System.currentTimeMillis());
+        peerData.put("left",       left);
+        peerData.put("lastSeen",   System.currentTimeMillis());
         redisCache.setCacheMap(peerKey, peerData);
-        redisCache.expire(peerKey, 1200, TimeUnit.SECONDS);
+        redisCache.expire(peerKey, 120, TimeUnit.SECONDS);
 
-        // 4. 收集 peers,根据自身状态区分返回哪些 peer
-        boolean isSeeder = left == 0;
+        // 4. 从 Redis 中扫描所有同 info_hash 的 peer
         Collection<String> keys = redisCache.keys("peer:" + infoHashHex + ":*");
         List<byte[]> peersBin = new ArrayList<>(keys.size());
-
         for (String key : keys) {
             @SuppressWarnings("unchecked")
             Map<String, Object> data = (Map<String, Object>) redisCache.getCacheMap(key);
-            if (data == null) continue;
-
-            String otherPeerId = key.substring(key.lastIndexOf(":") + 1);
-            if (otherPeerId.equals(peerIdStr)) continue; // 忽略自己
-
             String peerIp = (String) data.get("ip");
-            int peerPort = ((Number) data.get("port")).intValue();
-            long otherLeft = ((Number) data.get("left")).longValue();
-
-            if (peerIp.contains(":")) continue; // 跳过 IPv6
-
-            // 按条件过滤
-            if (isSeeder && otherLeft > 0) {
-                peersBin.add(encodePeer(peerIp, peerPort));
-            } else if (!isSeeder) {
-                peersBin.add(encodePeer(peerIp, peerPort));
-            }
-
-            if (peersBin.size() >= 50) break;
+            int    peerPort = ((Number) data.get("port")).intValue();
+            peersBin.add(encodePeer(peerIp, peerPort));
+            if (peersBin.size() >= 50) break;  // 最多返回 50 个 peers
         }
 
-        // 4.5 合并 peers 到 compact 格式
-        ByteArrayOutputStream peerStream = new ByteArrayOutputStream();
-        for (byte[] peer : peersBin) {
-            if (peer != null && peer.length == 6) {
-                peerStream.write(peer);
-            }
-        }
-        byte[] compactPeers = peerStream.toByteArray();
-
-        // 5. 构造返回 Map
+        // 5. 构造返回数据 Map(有序)
         Map<String, Object> reply = new LinkedHashMap<>();
         reply.put("interval", ANNOUNCE_INTERVAL);
         reply.put("min interval", ANNOUNCE_INTERVAL / 2);
-        reply.put("complete", countSeeders(infoHashHex));
+        reply.put("complete",   countSeeders(infoHashHex));
         reply.put("incomplete", countLeechers(infoHashHex));
-        reply.put("peers", compactPeers);
+        reply.put("peers",      peersBin);
 
         return reply;
     }
 
-
     @Override
     public byte[] encodeBencode(Map<String, Object> reply) throws IOException {
         BencodeEncoder encoder = new BencodeEncoder();
@@ -134,22 +107,17 @@
         return count;
     }
 
+    /** 将 IPv4 + port 编码成 6 字节:4 字节 IP + 2 字节 port */
     private byte[] encodePeer(String ip, int port) throws Exception {
-        if (ip.contains(":")) return null; // 跳过 IPv6
-
         String[] parts = ip.split("\\.");
-        if (parts.length != 4) throw new IllegalArgumentException("无效的 IPv4 地址: " + ip);
-
         ByteBuffer buf = ByteBuffer.allocate(6);
-        for (String part : parts) {
-            buf.put((byte) Integer.parseInt(part));
+        for (int i = 0; i < 4; i++) {
+            buf.put((byte) Integer.parseInt(parts[i]));
         }
         buf.putShort((short) port);
         return buf.array();
     }
 
-
-
     /** 将字节数组转成十六进制字符串 */
     private static final char[] HEX = "0123456789abcdef".toCharArray();
     private String bytesToHex(byte[] bytes) {
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java
index c937042..a2cdb30 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java
@@ -2,7 +2,6 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 public class BencodeEncoder {
@@ -33,20 +32,11 @@
             encodeMap((Map<String, Object>) obj, outputStream);
         } else if (obj instanceof Iterable) {
             encodeList((Iterable<Object>) obj, outputStream);
-        } else if (obj instanceof byte[]) {
-            encodeByteArray((byte[]) obj, outputStream);
         } else {
             throw new IllegalArgumentException("Unsupported object type: " + obj.getClass());
         }
     }
 
-    private void encodeByteArray(byte[] bytes, ByteArrayOutputStream outputStream) throws IOException {
-        outputStream.write(Integer.toString(bytes.length).getBytes());
-        outputStream.write(':');
-        outputStream.write(bytes);
-    }
-
-
     private void encodeInteger(Integer value, ByteArrayOutputStream outputStream) throws IOException {
         outputStream.write('i');
         outputStream.write(value.toString().getBytes());
@@ -54,13 +44,11 @@
     }
 
     private void encodeString(String value, ByteArrayOutputStream outputStream) throws IOException {
-        byte[] bytes = value.getBytes(StandardCharsets.UTF_8); // Assuming UTF-8 encoding
-        outputStream.write(Integer.toString(bytes.length).getBytes());
+        outputStream.write(Integer.toString(value.length()).getBytes());
         outputStream.write(':');
-        outputStream.write(bytes);
+        outputStream.write(value.getBytes());
     }
 
-
     private void encodeList(Iterable<Object> list, ByteArrayOutputStream outputStream) throws IOException {
         outputStream.write('l'); // Start of a list
 
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/reward/controller/RewardTasksController.java b/ruoyi-admin/src/main/java/com/ruoyi/reward/controller/RewardTasksController.java
new file mode 100644
index 0000000..bae499a
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/reward/controller/RewardTasksController.java
@@ -0,0 +1,104 @@
+package com.ruoyi.reward.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.reward.domain.RewardTasks;
+import com.ruoyi.reward.service.IRewardTasksService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 【请填写功能名称】Controller
+ * 
+ * @author ruoyi
+ * @date 2025-05-17
+ */
+@RestController
+@RequestMapping("/reward")
+public class RewardTasksController extends BaseController
+{
+    @Autowired
+    private IRewardTasksService rewardTasksService;
+
+    /**
+     * 查询【请填写功能名称】列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:tasks:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(RewardTasks rewardTasks)
+    {
+        startPage();
+        List<RewardTasks> list = rewardTasksService.selectRewardTasksList(rewardTasks);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出【请填写功能名称】列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:tasks:export')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, RewardTasks rewardTasks)
+    {
+        List<RewardTasks> list = rewardTasksService.selectRewardTasksList(rewardTasks);
+        ExcelUtil<RewardTasks> util = new ExcelUtil<RewardTasks>(RewardTasks.class);
+        util.exportExcel(response, list, "【请填写功能名称】数据");
+    }
+
+    /**
+     * 获取【请填写功能名称】详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:tasks:query')")
+    @GetMapping(value = "/{rewardId}")
+    public AjaxResult getInfo(@PathVariable("rewardId") Long rewardId)
+    {
+        return success(rewardTasksService.selectRewardTasksByRewardId(rewardId));
+    }
+
+    /**
+     * 新增【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:tasks:add')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody RewardTasks rewardTasks)
+    {
+        return toAjax(rewardTasksService.insertRewardTasks(rewardTasks));
+    }
+
+    /**
+     * 修改【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:tasks:edit')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody RewardTasks rewardTasks)
+    {
+        return toAjax(rewardTasksService.updateRewardTasks(rewardTasks));
+    }
+
+    /**
+     * 删除【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:tasks:remove')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{rewardIds}")
+    public AjaxResult remove(@PathVariable Long[] rewardIds)
+    {
+        return toAjax(rewardTasksService.deleteRewardTasksByRewardIds(rewardIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/reward/domain/RewardTasks.java b/ruoyi-admin/src/main/java/com/ruoyi/reward/domain/RewardTasks.java
new file mode 100644
index 0000000..50e5c8f
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/reward/domain/RewardTasks.java
@@ -0,0 +1,125 @@
+package com.ruoyi.reward.domain;
+
+import java.math.BigDecimal;
+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;
+
+/**
+ * 【请填写功能名称】对象 reward_tasks
+ * 
+ * @author ruoyi
+ * @date 2025-05-17
+ */
+public class RewardTasks extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long rewardId;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String title;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String description;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private BigDecimal amount;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String status;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Long publisherId;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Long accepterId;
+
+    public void setRewardId(Long rewardId) 
+    {
+        this.rewardId = rewardId;
+    }
+
+    public Long getRewardId() 
+    {
+        return rewardId;
+    }
+    public void setTitle(String title) 
+    {
+        this.title = title;
+    }
+
+    public String getTitle() 
+    {
+        return title;
+    }
+    public void setDescription(String description) 
+    {
+        this.description = description;
+    }
+
+    public String getDescription() 
+    {
+        return description;
+    }
+    public void setAmount(BigDecimal amount) 
+    {
+        this.amount = amount;
+    }
+
+    public BigDecimal getAmount() 
+    {
+        return amount;
+    }
+    public void setStatus(String status) 
+    {
+        this.status = status;
+    }
+
+    public String getStatus() 
+    {
+        return status;
+    }
+    public void setPublisherId(Long publisherId) 
+    {
+        this.publisherId = publisherId;
+    }
+
+    public Long getPublisherId() 
+    {
+        return publisherId;
+    }
+    public void setAccepterId(Long accepterId) 
+    {
+        this.accepterId = accepterId;
+    }
+
+    public Long getAccepterId() 
+    {
+        return accepterId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("rewardId", getRewardId())
+            .append("title", getTitle())
+            .append("description", getDescription())
+            .append("amount", getAmount())
+            .append("status", getStatus())
+            .append("publisherId", getPublisherId())
+            .append("accepterId", getAccepterId())
+            .append("createTime", getCreateTime())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/reward/mapper/RewardTasksMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/reward/mapper/RewardTasksMapper.java
new file mode 100644
index 0000000..92520b8
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/reward/mapper/RewardTasksMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.reward.mapper;
+
+import java.util.List;
+import com.ruoyi.reward.domain.RewardTasks;
+
+/**
+ * 【请填写功能名称】Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-05-17
+ */
+public interface RewardTasksMapper 
+{
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param rewardId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    public RewardTasks selectRewardTasksByRewardId(Long rewardId);
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 【请填写功能名称】集合
+     */
+    public List<RewardTasks> selectRewardTasksList(RewardTasks rewardTasks);
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 结果
+     */
+    public int insertRewardTasks(RewardTasks rewardTasks);
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 结果
+     */
+    public int updateRewardTasks(RewardTasks rewardTasks);
+
+    /**
+     * 删除【请填写功能名称】
+     * 
+     * @param rewardId 【请填写功能名称】主键
+     * @return 结果
+     */
+    public int deleteRewardTasksByRewardId(Long rewardId);
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param rewardIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteRewardTasksByRewardIds(Long[] rewardIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/reward/service/IRewardTasksService.java b/ruoyi-admin/src/main/java/com/ruoyi/reward/service/IRewardTasksService.java
new file mode 100644
index 0000000..5b8bd13
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/reward/service/IRewardTasksService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.reward.service;
+
+import java.util.List;
+import com.ruoyi.reward.domain.RewardTasks;
+
+/**
+ * 【请填写功能名称】Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-05-17
+ */
+public interface IRewardTasksService 
+{
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param rewardId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    public RewardTasks selectRewardTasksByRewardId(Long rewardId);
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 【请填写功能名称】集合
+     */
+    public List<RewardTasks> selectRewardTasksList(RewardTasks rewardTasks);
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 结果
+     */
+    public int insertRewardTasks(RewardTasks rewardTasks);
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 结果
+     */
+    public int updateRewardTasks(RewardTasks rewardTasks);
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param rewardIds 需要删除的【请填写功能名称】主键集合
+     * @return 结果
+     */
+    public int deleteRewardTasksByRewardIds(Long[] rewardIds);
+
+    /**
+     * 删除【请填写功能名称】信息
+     * 
+     * @param rewardId 【请填写功能名称】主键
+     * @return 结果
+     */
+    public int deleteRewardTasksByRewardId(Long rewardId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/reward/service/impl/RewardTasksServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/reward/service/impl/RewardTasksServiceImpl.java
new file mode 100644
index 0000000..b80f3f3
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/reward/service/impl/RewardTasksServiceImpl.java
@@ -0,0 +1,96 @@
+package com.ruoyi.reward.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.reward.mapper.RewardTasksMapper;
+import com.ruoyi.reward.domain.RewardTasks;
+import com.ruoyi.reward.service.IRewardTasksService;
+
+/**
+ * 【请填写功能名称】Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-05-17
+ */
+@Service
+public class RewardTasksServiceImpl implements IRewardTasksService 
+{
+    @Autowired
+    private RewardTasksMapper rewardTasksMapper;
+
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param rewardId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    @Override
+    public RewardTasks selectRewardTasksByRewardId(Long rewardId)
+    {
+        return rewardTasksMapper.selectRewardTasksByRewardId(rewardId);
+    }
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 【请填写功能名称】
+     */
+    @Override
+    public List<RewardTasks> selectRewardTasksList(RewardTasks rewardTasks)
+    {
+        return rewardTasksMapper.selectRewardTasksList(rewardTasks);
+    }
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 结果
+     */
+    @Override
+    public int insertRewardTasks(RewardTasks rewardTasks)
+    {
+        rewardTasks.setCreateTime(DateUtils.getNowDate());
+        return rewardTasksMapper.insertRewardTasks(rewardTasks);
+    }
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param rewardTasks 【请填写功能名称】
+     * @return 结果
+     */
+    @Override
+    public int updateRewardTasks(RewardTasks rewardTasks)
+    {
+        rewardTasks.setUpdateTime(DateUtils.getNowDate());
+        return rewardTasksMapper.updateRewardTasks(rewardTasks);
+    }
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param rewardIds 需要删除的【请填写功能名称】主键
+     * @return 结果
+     */
+    @Override
+    public int deleteRewardTasksByRewardIds(Long[] rewardIds)
+    {
+        return rewardTasksMapper.deleteRewardTasksByRewardIds(rewardIds);
+    }
+
+    /**
+     * 删除【请填写功能名称】信息
+     * 
+     * @param rewardId 【请填写功能名称】主键
+     * @return 结果
+     */
+    @Override
+    public int deleteRewardTasksByRewardId(Long rewardId)
+    {
+        return rewardTasksMapper.deleteRewardTasksByRewardId(rewardId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
index 6d7626b..46345c7 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
@@ -68,7 +68,6 @@
     @PostMapping("/uploadTorrent")
     public AjaxResult uploadTorrent(@RequestParam("file") MultipartFile file) {
         try {
-
             // Create URL connection to Flask server
             String flaskUrl = "http://localhost:5000/parse_torrent"; // Flask server URL
             HttpURLConnection connection = (HttpURLConnection) new URL(flaskUrl).openConnection();
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index c86c6b3..cb1a87c 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -9,7 +9,7 @@
   # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
   profile: D:/ruoyi/uploadPath
   # 获取ip地址开关
-  addressEnabled: true
+  addressEnabled: false
   # 验证码类型 math 数字计算 char 字符验证
   captchaType: math
 
diff --git a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
index afb29d7..e19128a 100644
--- a/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
+++ b/ruoyi-admin/src/main/resources/mapper/system/BtTorrentMapper.xml
@@ -24,7 +24,9 @@
 
     <select id="selectBtTorrentList" parameterType="BtTorrent" resultMap="BtTorrentResult">
         <include refid="selectBtTorrentVo"/>
-        <where>  
+        <where>
+            <if test="torrentId != null  and torrentId != ''"> and torrent_id = #{torrentId}</if>
+
             <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>
diff --git a/ruoyi-admin/src/main/resources/mapper/system/RewardTasksMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/RewardTasksMapper.xml
new file mode 100644
index 0000000..bae8185
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/RewardTasksMapper.xml
@@ -0,0 +1,93 @@
+<?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.reward.mapper.RewardTasksMapper">
+    
+    <resultMap type="RewardTasks" id="RewardTasksResult">
+        <result property="rewardId"    column="reward_id"    />
+        <result property="title"    column="title"    />
+        <result property="description"    column="description"    />
+        <result property="amount"    column="amount"    />
+        <result property="status"    column="status"    />
+        <result property="publisherId"    column="publisher_id"    />
+        <result property="accepterId"    column="accepter_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectRewardTasksVo">
+        select reward_id, title, description, amount, status, publisher_id, accepter_id, create_time, update_time, remark from reward_tasks
+    </sql>
+
+    <select id="selectRewardTasksList" parameterType="RewardTasks" resultMap="RewardTasksResult">
+        <include refid="selectRewardTasksVo"/>
+        <where>  
+            <if test="title != null  and title != ''"> and title = #{title}</if>
+            <if test="description != null  and description != ''"> and description = #{description}</if>
+            <if test="amount != null "> and amount = #{amount}</if>
+            <if test="status != null  and status != ''"> and status = #{status}</if>
+            <if test="publisherId != null "> and publisher_id = #{publisherId}</if>
+            <if test="accepterId != null "> and accepter_id = #{accepterId}</if>
+        </where>
+    </select>
+    
+    <select id="selectRewardTasksByRewardId" parameterType="Long" resultMap="RewardTasksResult">
+        <include refid="selectRewardTasksVo"/>
+        where reward_id = #{rewardId}
+    </select>
+
+    <insert id="insertRewardTasks" parameterType="RewardTasks" useGeneratedKeys="true" keyProperty="rewardId">
+        insert into reward_tasks
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="title != null and title != ''">title,</if>
+            <if test="description != null and description != ''">description,</if>
+            <if test="amount != null">amount,</if>
+            <if test="status != null">status,</if>
+            <if test="publisherId != null">publisher_id,</if>
+            <if test="accepterId != null">accepter_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="title != null and title != ''">#{title},</if>
+            <if test="description != null and description != ''">#{description},</if>
+            <if test="amount != null">#{amount},</if>
+            <if test="status != null">#{status},</if>
+            <if test="publisherId != null">#{publisherId},</if>
+            <if test="accepterId != null">#{accepterId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateRewardTasks" parameterType="RewardTasks">
+        update reward_tasks
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="title != null and title != ''">title = #{title},</if>
+            <if test="description != null and description != ''">description = #{description},</if>
+            <if test="amount != null">amount = #{amount},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="publisherId != null">publisher_id = #{publisherId},</if>
+            <if test="accepterId != null">accepter_id = #{accepterId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where reward_id = #{rewardId}
+    </update>
+
+    <delete id="deleteRewardTasksByRewardId" parameterType="Long">
+        delete from reward_tasks where reward_id = #{rewardId}
+    </delete>
+
+    <delete id="deleteRewardTasksByRewardIds" parameterType="String">
+        delete from reward_tasks where reward_id in 
+        <foreach item="rewardId" collection="array" open="(" separator="," close=")">
+            #{rewardId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
index 378c8eb..330039f 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -111,7 +111,7 @@
             .authorizeHttpRequests((requests) -> {
                 permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                requests.requestMatchers("/login", "/register", "/captchaImage","/announce").permitAll()
+                requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
                     // 静态资源,可匿名访问
                     .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
                     .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()