种子,促销

Change-Id: I0ce919ce4228dcefec26ef636bacd3298c0dc77a
diff --git a/src/main/java/com/example/myproject/MyProjectApplication.java b/src/main/java/com/example/myproject/MyProjectApplication.java
index 49cd5f0..eb58082 100644
--- a/src/main/java/com/example/myproject/MyProjectApplication.java
+++ b/src/main/java/com/example/myproject/MyProjectApplication.java
@@ -5,10 +5,14 @@
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
+
+
 @SpringBootApplication
 @MapperScan("com.example.myproject.mapper")  // 扫描 Mapper 接口
+
 public class MyProjectApplication {
     public static void main(String[] args) {
         SpringApplication.run(MyProjectApplication.class, args);
     }
 }
+
diff --git a/src/main/java/com/example/myproject/common/CommonResultStatus.java b/src/main/java/com/example/myproject/common/CommonResultStatus.java
new file mode 100644
index 0000000..76e63a6
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/CommonResultStatus.java
@@ -0,0 +1,37 @@
+package com.example.myproject.common;
+
+
+public enum CommonResultStatus implements ResultStatus {
+
+    OK(0, "成功"),
+
+    FAIL(500, "失败"),
+
+    PARAM_ERROR(400, "参数非法"),
+
+    RECORD_NOT_EXIST(404, "记录不存在"),
+
+    UNAUTHORIZED(401, "未授权"),
+
+    FORBIDDEN(403, "无权限"),
+
+    SERVER_ERROR(500, "服务器内部错误");
+
+    private final int code;
+    private final String message;
+
+    CommonResultStatus(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    @Override
+    public int getCode() {
+        return code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+}
diff --git a/src/main/java/com/example/myproject/common/Constants.java b/src/main/java/com/example/myproject/common/Constants.java
new file mode 100644
index 0000000..4e3d864
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/Constants.java
@@ -0,0 +1,54 @@
+package com.example.myproject.common;
+
+
+public interface Constants {
+
+    String TOKEN_HEADER_NAME = "Authorization";
+    String SESSION_CURRENT_USER = "currentUser";
+
+    /**
+     * 菜单根id
+     */
+    Integer RESOURCE_ROOT_ID = 0;
+
+    interface Order {
+        String DEFAULT_ORDER_TYPE = "desc";
+
+        String[] ORDER_TYPE = new String[]{"desc", "asc", "DESC", "ASC"};
+    }
+
+    interface FinishStatus {
+
+        /**
+         * 已完成并测试
+         */
+        String FINISHED = " (已完成并测试通过)";
+
+        /**
+         * 未完成未测试
+         */
+        String UNFINISHED = " (未完成未测试)";
+
+        /**
+         * 已完成但未测试
+         */
+        String FINISHED_NOT_TEST = " (已完成但未测试)";
+
+    }
+
+    interface Source {
+        String PREFIX = "[RKT] ";
+
+        String NAME = "rocket pt";
+    }
+
+    interface Announce {
+
+        String PROTOCOL = "http";
+
+        String HOSTNAME = "192.168.6.112";
+
+        Integer PORT = 9966;
+
+    }
+}
diff --git a/src/main/java/com/example/myproject/common/ResultStatus.java b/src/main/java/com/example/myproject/common/ResultStatus.java
new file mode 100644
index 0000000..f6c7afe
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/ResultStatus.java
@@ -0,0 +1,14 @@
+package com.example.myproject.common;
+
+
+public interface ResultStatus {
+    /**
+     * 错误码
+     */
+    int getCode();
+
+    /**
+     * 错误信息
+     */
+    String getMessage();
+}
diff --git a/src/main/java/com/example/myproject/common/base/I18nMessage.java b/src/main/java/com/example/myproject/common/base/I18nMessage.java
new file mode 100644
index 0000000..a725e9b
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/I18nMessage.java
@@ -0,0 +1,61 @@
+package com.example.myproject.common.base;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+
+import java.util.Objects;
+
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@UtilityClass
+public class I18nMessage {
+
+    static {
+        ReloadableResourceBundleMessageSource messageSource =
+                new ReloadableResourceBundleMessageSource();
+        messageSource.setCacheSeconds(5);
+        messageSource.setBasenames("classpath:i18n/message");
+        I18nMessage.init(messageSource);
+    }
+
+    private static MessageSource messageSource;
+
+    public static void init(MessageSource messageSource) {
+        Objects.requireNonNull(messageSource, "MessageSource can't be null");
+        I18nMessage.messageSource = messageSource;
+    }
+
+    /**
+     * 读取国际化消息
+     *
+     * @param msgCode 消息码
+     * @param args    消息参数 例: new String[]{"1","2","3"}
+     * @return
+     */
+    public static String getMessage(String msgCode, Object[] args) {
+        try {
+            return I18nMessage.messageSource.getMessage(msgCode, args,
+                    LocaleContextHolder.getLocale());
+        } catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                e.printStackTrace();
+            }
+            log.error("===> 读取国际化消息失败, code:{}, args:{}, ex:{}", msgCode, args,
+                    e.getMessage() == null ? e.toString() : e.getMessage());
+        }
+        return "-Unknown-";
+    }
+
+    /**
+     * 获取一条语言配置信息
+     *
+     * @param msgCode 消息码
+     * @return 对应配置的信息
+     */
+    public static String getMessage(String msgCode) {
+        return I18nMessage.getMessage(msgCode, null);
+    }
+}
diff --git a/src/main/java/com/example/myproject/common/base/OrderPageParam.java b/src/main/java/com/example/myproject/common/base/OrderPageParam.java
new file mode 100644
index 0000000..21ff38b
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/OrderPageParam.java
@@ -0,0 +1,99 @@
+package com.example.myproject.common.base;
+
+
+import com.example.myproject.common.CommonResultStatus;
+import com.example.myproject.common.Constants;
+import com.example.myproject.common.exception.RocketPTException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class OrderPageParam extends PageParam {
+
+    /**
+     * 排序字段
+     */
+    @Schema(description = "排序字段")
+    protected String prop;
+
+    /**
+     * 排序规则
+     */
+    @Schema(description = "排序规则")
+    protected String sort;
+
+    public void validOrder(List<String> orderKey) throws RocketPTException {
+        prop = StringUtils.isBlank(prop) ? null : StrUtil.toUnderlineCase(prop);
+        sort = StringUtils.isBlank(sort) ? Constants.Order.DEFAULT_ORDER_TYPE : sort;
+
+        if (Arrays.asList(Constants.Order.ORDER_TYPE).indexOf(sort) < 0) {
+            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序方式錯誤");
+        }
+
+        if (StringUtils.isNotBlank(prop) && Arrays.asList(orderKey).indexOf(prop) < 0) {
+            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序欄位錯誤");
+        }
+    }
+
+    public void validOrder() throws RocketPTException {
+        List<String> orderKey = getOrderKey();
+        prop = StringUtils.isBlank(prop) ? null : StrUtil.toUnderlineCase(prop);
+        sort = StringUtils.isBlank(sort) ? Constants.Order.DEFAULT_ORDER_TYPE : sort;
+
+        if (!ArrayUtil.contains(Constants.Order.ORDER_TYPE, sort)) {
+            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序方式錯誤");
+        }
+
+        if (StringUtils.isNotBlank(prop) && !orderKey.contains(prop)) {
+            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序欄位錯誤");
+        }
+    }
+
+    /**
+     * @return 反射获取字段列表
+     */
+    public List<String> getOrderKey() {
+        List<String> list = new ArrayList<>();
+
+        Field[] fields = getClass().getDeclaredFields();
+        if (fields != null) {
+            for (Field field : fields) {
+                field.setAccessible(true);
+                String name = field.getName();
+
+                list.add(StrUtil.toUnderlineCase(name));
+            }
+        }
+        return list;
+    }
+    /**
+     * @return 反射获取字段列表
+     */
+    public List<String> getOrderKey(Class clazz) {
+        List<String> list = new ArrayList<>();
+
+        Field[] fields = clazz.getDeclaredFields();
+        if (fields != null) {
+            for (Field field : fields) {
+                field.setAccessible(true);
+                String name = field.getName();
+
+                list.add(StrUtil.toUnderlineCase(name));
+            }
+        }
+        return list;
+    }
+
+}
diff --git a/src/main/java/com/example/myproject/common/base/PageParam.java b/src/main/java/com/example/myproject/common/base/PageParam.java
new file mode 100644
index 0000000..c59108d
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/PageParam.java
@@ -0,0 +1,25 @@
+package com.example.myproject.common.base;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+
+@Getter
+@Setter
+@ToString
+public class PageParam {
+    @NotNull(message = "page参数不能为空")
+    @Min(value = 1L, message = "page参数必须是数字或数值小于限制")
+    protected Integer page;
+
+    @NotNull(message = "参数不能为空")
+    @Min(value = 1L, message = "参数必须是数字或数值小于限制")
+    @Max(value = 200L, message = "参数必须是数字或数值大于限制")
+    protected Integer size;
+
+
+}
diff --git a/src/main/java/com/example/myproject/common/base/PageUtil.java b/src/main/java/com/example/myproject/common/base/PageUtil.java
new file mode 100644
index 0000000..a4b51ce
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/PageUtil.java
@@ -0,0 +1,54 @@
+package com.example.myproject.common.base;
+
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+
+import java.util.List;
+
+public class PageUtil {
+
+
+    public static int DEFAULT_PAGE_SIZE = 20;
+
+    /**
+     * 开始分页
+     *
+     * @param param
+     */
+    public static void startPage(OrderPageParam param) {
+        Integer page = param.getPage();
+        if (page == null) {
+            param.setPage(1);
+            param.setSize(DEFAULT_PAGE_SIZE);
+        }
+
+        PageHelper.startPage(param.getPage(), param.getSize());
+
+    }
+
+    /**
+     * 开始分页
+     *
+     * @param param
+     */
+    public static void startPage(PageParam param) {
+        Integer page = param.getPage();
+        if (page == null) {
+            param.setPage(1);
+            param.setSize(DEFAULT_PAGE_SIZE);
+        }
+
+        PageHelper.startPage(param.getPage(), param.getSize());
+    }
+
+    /**
+     * 分页结果
+     *
+     * @param list
+     */
+    public static ResPage getPage(List list) {
+        PageInfo pageInfo = new PageInfo(list);
+        return new ResPage(pageInfo.getTotal(), pageInfo.getPageNum(), pageInfo.getSize());
+
+    }
+}
diff --git a/src/main/java/com/example/myproject/common/base/ResPage.java b/src/main/java/com/example/myproject/common/base/ResPage.java
new file mode 100644
index 0000000..45a22dd
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/ResPage.java
@@ -0,0 +1,31 @@
+package com.example.myproject.common.base;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 分页的返回值
+ */
+@Getter
+@Setter
+@ToString(callSuper = false)
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResPage {
+
+    private long total;
+
+    private int page;
+
+    private int size;
+
+    public static ResPage getPage(long total, int page, int size) {
+        return new ResPage(total, page, size);
+    }
+
+    public static ResPage defaultPage() {
+        return new ResPage(10, 1, 10);
+    }
+}
diff --git a/src/main/java/com/example/myproject/common/base/Result.java b/src/main/java/com/example/myproject/common/base/Result.java
new file mode 100644
index 0000000..6d20f4f
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/Result.java
@@ -0,0 +1,178 @@
+package com.example.myproject.common.base;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 返回值实体类
+ */
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+public class Result<T> {
+
+    private int code;
+
+    private String msg;
+
+    @JsonProperty
+    private T data;
+
+    private ResPage page;
+
+
+    public Result(Status status) {
+        this.code = status.getCode();
+        this.msg = status.getMsg();
+        this.data = null;
+    }
+
+    public Result(Status status, T data) {
+        this.code = status.getCode();
+        this.msg = status.getMsg();
+        this.data = data;
+    }
+
+    public Result(Status status, String msg) {
+        this.code = status.getCode();
+        this.msg = msg;
+        this.data = null;
+    }
+
+    public Result(Status status, int msgCode) {
+        this.code = status.getCode();
+        this.msg = I18nMessage.getMessage(String.valueOf(msgCode));
+        this.data = null;
+    }
+
+    public Result(Status status, String msg, T data) {
+        this.code = status.getCode();
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public Result(Status status, int msgCode, T data) {
+        this.code = status.getCode();
+        this.msg = I18nMessage.getMessage(String.valueOf(msgCode));
+        this.data = data;
+    }
+
+    public Result(Status status, T data, ResPage page) {
+        this.code = status.getCode();
+        this.msg = status.getMsg();
+        this.data = data;
+        this.page = page;
+    }
+
+    public Result(Status status, String msg, T data, ResPage page) {
+        this.code = status.getCode();
+        this.msg = msg;
+        this.data = data;
+        this.page = page;
+    }
+
+    public Result(Status status, int msgCode, T data, ResPage page) {
+        this.code = status.getCode();
+        this.msg = I18nMessage.getMessage(String.valueOf(msgCode));
+        this.data = data;
+        this.page = page;
+    }
+
+    @JsonIgnore
+    public boolean isSuccess() {
+        return this.code == Status.SUCCESS.getCode();
+    }
+
+    @JsonIgnore
+    public boolean nonSuccess() {
+        return this.code != Status.SUCCESS.getCode();
+    }
+
+    public static <T> Result<T> success(String 取消收藏成功) {
+        return new Result<T>(Status.SUCCESS);
+    }
+
+    public static <T> Result<T> ok() {
+        return new Result<T>(Status.SUCCESS);
+    }
+
+    public static <T> Result<T> ok(T data) {
+        return new Result<T>(Status.SUCCESS, data);
+    }
+
+    public static <T> Result<T> illegal() {
+        return new Result<T>(Status.BAD_REQUEST);
+    }
+
+    public static <T> Result<T> unauthorized() {
+        return new Result<T>(Status.UNAUTHORIZED);
+    }
+
+    public static <T> Result<T> forbidden() {
+        return new Result<T>(Status.FORBIDDEN);
+    }
+
+    public static <T> Result<T> notFound() {
+        return new Result<T>(Status.NOT_FOUND);
+    }
+
+    public static <T> Result<T> failure() {
+        return new Result<T>(Status.FAILURE);
+    }
+
+    public static <T> Result<T> failure(String msg) {
+        return new Result<T>(Status.FAILURE, msg);
+    }
+
+    public static <T> Result<T> error(String msg) {
+        return new Result<T>(Status.FAILURE, msg);
+    }
+
+    public static <T> Result<T> conflict() {
+        return new Result<T>(Status.CONFLICT);
+    }
+
+    public static <T> Result<T> build(Status status, T data) {
+        return new Result<T>(status, data);
+    }
+
+    public static <T> Result<T> build(Status status, String msg) {
+        return new Result<T>(status, msg);
+    }
+
+    public static <T> Result<T> build(Status status, int msgCode) {
+        return new Result<T>(status, msgCode);
+    }
+
+    public static <T> Result<T> build(Status status, String msg, T data) {
+        return new Result<T>(status, msg, data);
+    }
+
+    public static <T> Result<T> build(Status status, int msgCode, T data) {
+        return new Result<T>(status, msgCode, data);
+    }
+
+    public static Result ok(Object data, ResPage page) {
+        return new Result(Status.SUCCESS, data, page);
+    }
+
+    public static Result build(Status status, Object data, ResPage page) {
+        return new Result(status, data, page);
+    }
+
+    public static Result build(Status status, String msg, Object data, ResPage page) {
+        return new Result(status, msg, data, page);
+    }
+
+    public static Result build(Status status, int msgCode, Object data, ResPage page) {
+        return new Result(status, msgCode, data, page);
+    }
+
+}
diff --git a/src/main/java/com/example/myproject/common/base/Status.java b/src/main/java/com/example/myproject/common/base/Status.java
new file mode 100644
index 0000000..4949d0c
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/base/Status.java
@@ -0,0 +1,57 @@
+package com.example.myproject.common.base;
+
+
+public enum Status {
+
+    /**
+     * 请求执行成功
+     */
+    SUCCESS(0, "操作成功"),
+
+    /**
+     * 请求验证失败
+     */
+    BAD_REQUEST(400, "操作验证失败"),
+
+    /**
+     * 权限不足
+     */
+    UNAUTHORIZED(401, "操作未授权"),
+
+    /**
+     * 请求拒绝
+     */
+    FORBIDDEN(403, "操作被拒绝"),
+
+    /**
+     * 未知请求
+     */
+    NOT_FOUND(404, "未知操作"),
+
+    /**
+     * 未知请求
+     */
+    CONFLICT(409, "请求发生冲突"),
+
+    /**
+     * 请求执行失败
+     */
+    FAILURE(500, "操作失败");
+
+    private int code;
+
+    private String msg;
+
+    Status(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return this.code;
+    }
+
+    public String getMsg() {
+        return this.msg;
+    }
+}
diff --git a/src/main/java/com/example/myproject/common/exception/RocketPTException.java b/src/main/java/com/example/myproject/common/exception/RocketPTException.java
new file mode 100644
index 0000000..7a475f2
--- /dev/null
+++ b/src/main/java/com/example/myproject/common/exception/RocketPTException.java
@@ -0,0 +1,29 @@
+package com.example.myproject.common.exception;
+
+import com.example.myproject.common.CommonResultStatus;
+import com.example.myproject.common.ResultStatus;
+
+public class RocketPTException extends RuntimeException {
+    private final ResultStatus status;
+
+    public RocketPTException(ResultStatus status) {
+        super(status.getMessage());
+        this.status = status;
+    }
+
+    public RocketPTException(ResultStatus status, String message) {
+        super(message);
+        this.status = status;
+    }
+
+    public RocketPTException(String message) {
+        super(message);
+        this.status = CommonResultStatus.FAIL;
+    }
+
+    public ResultStatus getStatus() {
+        return status;
+    }
+
+
+}
diff --git a/src/main/java/com/example/myproject/config/MyMetaObjectHandler.java b/src/main/java/com/example/myproject/config/MyMetaObjectHandler.java
new file mode 100644
index 0000000..1031b93
--- /dev/null
+++ b/src/main/java/com/example/myproject/config/MyMetaObjectHandler.java
@@ -0,0 +1,24 @@
+package com.example.myproject.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
+        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
+    }
+
+    // 更新时自动填充
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
+    }
+}
diff --git a/src/main/java/com/example/myproject/config/TrackerConfig.java b/src/main/java/com/example/myproject/config/TrackerConfig.java
new file mode 100644
index 0000000..67522b7
--- /dev/null
+++ b/src/main/java/com/example/myproject/config/TrackerConfig.java
@@ -0,0 +1,81 @@
+package com.example.myproject.config;
+
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.turn.ttorrent.tracker.Tracker;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import javax.annotation.PreDestroy;
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+
+@Configuration
+public class TrackerConfig {
+    @Value("${pt.tracker.port}")
+    private int trackerPort;
+
+    @Value("${pt.tracker.torrent-dir}")
+    private String torrentDir;
+    @Value("${pt.tracker.announce-url}")
+    private String announceURL;
+
+    private Tracker tracker; // 存储 Tracker 实例
+
+    @Bean
+    public Tracker torrentTracker() throws IOException {
+        // 验证并创建目录
+        File dir = new File(torrentDir);
+        validateTorrentDirectory(dir);
+
+        // 初始化 Tracker
+        tracker = new Tracker(trackerPort, announceURL);
+        tracker.setAcceptForeignTorrents(false); // PT 站点必须关闭匿名模式
+        tracker.setAnnounceInterval(1800); // 30分钟 announce 间隔
+
+        // 加载种子文件
+        loadTorrents(tracker, dir);
+
+        // 启动 Tracker
+        tracker.start(true);
+        System.out.println("Tracker started on port " + trackerPort);
+        return tracker;
+    }
+
+    private void validateTorrentDirectory(File dir) throws IOException {
+        if (!dir.exists()) {
+            if (!dir.mkdirs()) {
+                throw new IOException("无法创建目录: " + dir.getAbsolutePath());
+            }
+        }
+        if (!dir.isDirectory()) {
+            throw new IllegalArgumentException("路径不是目录: " + dir.getAbsolutePath());
+        }
+        if (!dir.canRead() || !dir.canWrite()) {
+            throw new IOException("应用程序无权限访问目录: " + dir.getAbsolutePath());
+        }
+    }
+
+    private void loadTorrents(Tracker tracker, File dir) throws IOException {
+        if (!dir.exists() || !dir.isDirectory()) {
+            throw new IOException("无效的种子目录: " + dir.getAbsolutePath());
+        }
+
+        File[] torrentFiles = dir.listFiles((d, name) -> name.endsWith(".torrent"));
+        if (torrentFiles == null) {
+            throw new IOException("无法读取目录内容: " + dir.getAbsolutePath());
+        }
+
+        for (File f : torrentFiles) {
+            tracker.announce(TrackedTorrent.load(f));
+        }
+    }
+
+    @PreDestroy
+    public void stopTracker() {
+        if (tracker != null) {
+            tracker.stop();
+            System.out.println("Tracker stopped.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/controller/TorrentController.java b/src/main/java/com/example/myproject/controller/TorrentController.java
new file mode 100644
index 0000000..cea2ccf
--- /dev/null
+++ b/src/main/java/com/example/myproject/controller/TorrentController.java
@@ -0,0 +1,293 @@
+package com.example.myproject.controller;
+
+import com.example.myproject.common.base.PageUtil;
+import com.example.myproject.entity.TorrentEntity;
+import com.example.myproject.service.TorrentService;
+import com.example.myproject.service.PromotionService;
+import com.example.myproject.dto.param.TorrentParam;
+import com.example.myproject.dto.vo.TorrentVO;
+import com.example.myproject.common.base.Result;
+import com.example.myproject.dto.param.TorrentUploadParam;
+import com.example.myproject.dto.TorrentUpdateDTO;
+import com.example.myproject.dto.PromotionCreateDTO;
+import com.example.myproject.entity.Promotion;
+import com.example.myproject.service.UserService;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.stp.StpUtil;
+
+@RestController
+@RequestMapping("/seeds")
+public class TorrentController {
+
+    @Autowired
+    private TorrentService torrentService;
+
+    @Autowired
+    private PromotionService promotionService;
+
+    @Autowired
+    private UserService userService;
+
+
+    @SaCheckLogin
+    @Operation(summary = "种子列表查询", description = "种子列表条件查询-分页-排序")
+    @ApiResponse(responseCode = "0", description = "操作成功",
+            content = {@Content(mediaType = "application/json",
+                    schema = @Schema(implementation = TorrentVO.class))
+            })
+    @PostMapping("/list")
+    public Result list(@RequestBody TorrentParam param) {
+        // 构建排序和模糊查询条件
+        param.validOrder(param.getOrderKey(TorrentEntity.class));
+        param.buildLike();
+
+        PageUtil.startPage(param);
+
+        // 查询数据
+        List<TorrentEntity> list = torrentService.search(param);
+
+        // 返回分页结果
+        return Result.ok(list, PageUtil.getPage(list));
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "种子详情查询")
+    @ApiResponse(responseCode = "0", description = "操作成功", content = {
+            @Content(mediaType = "application/json", schema = @Schema(implementation =
+                    TorrentEntity.class))
+    })
+    @PostMapping("/info/{id}")
+    public Result info(@PathVariable("id")Long id) {
+
+        TorrentEntity entity = torrentService.selectBySeedId(id);
+        return Result.ok(entity);
+    }
+
+@Operation(summary = "上传种子")
+
+    @PostMapping("/upload")
+    public Result uploadTorrent(
+            @RequestParam("file") MultipartFile file,
+            @ModelAttribute @Validated TorrentUploadParam param) throws IOException {
+        try {
+            // 验证用户权限
+            // Long userId = StpUtil.getLoginIdAsLong();
+            String userId = String.valueOf(param.getUploader());
+            param.setUploader(userId);
+
+            // 验证文件大小和类型
+            if (file.isEmpty() || file.getSize() > 10 * 1024 * 1024) { // 10MB限制
+                return Result.error("文件大小不符合要求");
+            }
+
+            if (!file.getOriginalFilename().toLowerCase().endsWith(".torrent")) {
+                return Result.error("只支持.torrent文件");
+            }
+
+            torrentService.uploadTorrent(file, param);
+            return Result.ok();
+        } catch (Exception e) {
+            return Result.error("种子上传失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取种子文件
+     */
+    @GetMapping("/{seed_id}/download")
+    public void downloadTorrent(
+            @PathVariable("seed_id") Long seedId,
+            @RequestParam("passkey") String passkey,
+            HttpServletResponse response) throws IOException {
+
+        // 获取种子实体
+        TorrentEntity entity = torrentService.selectBySeedId(seedId);
+        if (entity == null) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND, "种子不存在");
+            return;
+        }
+
+        // 获取并处理种子文件内容(需在service中实现passkey注入)
+        byte[] torrentBytes = torrentService.fetch(seedId, passkey);
+
+        // 设置下载文件名
+        String filename = entity.getFileName();
+        if (filename == null || filename.isBlank()) {
+            filename = seedId + ".torrent";
+        }
+        if (!filename.toLowerCase().endsWith(".torrent")) {
+            filename = filename + ".torrent";
+        }
+        filename = java.net.URLEncoder.encode(filename, java.nio.charset.StandardCharsets.UTF_8).replaceAll("\\+",
+                "%20");
+
+        // 设置响应头
+        response.setCharacterEncoding(java.nio.charset.StandardCharsets.UTF_8.name());
+        response.setContentLength(torrentBytes.length);
+        response.setContentType("application/x-bittorrent");
+        response.setHeader("Content-Disposition", "attachment;filename=" + filename);
+
+        // 写入文件内容
+        response.getOutputStream().write(torrentBytes);
+        response.getOutputStream().flush();
+    }
+
+    /**
+     * 收藏或者取消收藏
+     */
+    @PostMapping("/{seed_id}/favorite-toggle")
+    public Result favorite(
+            @PathVariable("seed_id") Long seedId,
+            @RequestParam("user_id") Long  userId) {
+        try {
+
+            return  torrentService.favorite(seedId, userId);
+        } catch (Exception e) {
+            return Result.error("失败: ");
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "删除种子")
+    @DeleteMapping("/{torrentId}")
+    public Result deleteTorrent(@PathVariable Long torrentId) {
+        try {
+           //  验证用户权限
+            Long userId = StpUtil.getLoginIdAsLong();
+            if (!torrentService.canUserDeleteTorrent(torrentId, userId)) {
+                return Result.error("没有权限删除此种子");
+            }
+
+            torrentService.deleteTorrent(torrentId);
+            return Result.ok();
+        } catch (Exception e) {
+            return Result.error("删除失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "修改种子信息")
+    @PutMapping("/{torrentId}")
+    public Result updateTorrent(
+            @PathVariable Long torrentId,
+            @RequestBody @Validated TorrentUpdateDTO updateDTO) {
+        try {
+            // 验证用户权限
+            Long userId = StpUtil.getLoginIdAsLong();
+            if (!torrentService.canUserUpdateTorrent(torrentId, userId)) {
+                return Result.error("没有权限修改此种子");
+            }
+
+            torrentService.updateTorrent(torrentId, updateDTO);
+            return Result.ok();
+        } catch (Exception e) {
+            return Result.error("更新失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "创建促销活动")
+    @PostMapping("/promotions")
+    public Result createPromotion(@RequestBody @Validated PromotionCreateDTO promotionDTO) {
+        try {
+            // 验证用户权限(只有管理员可以创建促销)
+//            if (!StpUtil.hasRole("admin")) {
+//                return Result.error("没有权限创建促销活动");
+//            }
+//
+            Promotion promotion = promotionService.createPromotion(promotionDTO);
+            return Result.ok(promotion);
+        } catch (Exception e) {
+            return Result.error("创建促销失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "获取促销活动列表")
+    @GetMapping("/promotions")
+    public Result getPromotions() {
+        try {
+            List<Promotion> promotions = promotionService.getAllActivePromotions();
+            return Result.ok(promotions);
+        } catch (Exception e) {
+            return Result.error("获取促销列表失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "获取促销详情")
+    @GetMapping("/promotions/{promotionId}")
+    public Result getPromotionDetails(@PathVariable Long promotionId) {
+        try {
+            Promotion promotion = promotionService.getPromotionById(promotionId);
+            if (promotion == null) {
+                return Result.error("促销活动不存在");
+            }
+            return Result.ok(promotion);
+        } catch (Exception e) {
+            return Result.error("获取促销详情失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "删除促销活动")
+    @DeleteMapping("/promotions/{promotionId}")
+    public Result deletePromotion(@PathVariable Long promotionId) {
+        try {
+            // 验证用户权限(只有管理员可以删除促销)
+            if (!StpUtil.hasRole("admin")) {
+                return Result.error("没有权限删除促销活动");
+            }
+
+            promotionService.deletePromotion(promotionId);
+            return Result.ok();
+        } catch (Exception e) {
+            return Result.error("删除促销失败: " + e.getMessage());
+        }
+    }
+
+    // 下载种子(包含反作弊机制)
+    @PostMapping("/{torrentId}/download")
+    public ResponseEntity<?> downloadTorrent(@PathVariable Long torrentId,
+                                           @RequestParam Long userId) {
+//        // 验证用户身份和权限
+//        if (!userService.validateUser(userId)) {
+//            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+//        }
+
+        // 检查用户上传量是否足够
+        if (!torrentService.checkUserUploadRatio(userId)) {
+            return ResponseEntity.status(HttpStatus.FORBIDDEN)
+                .body("上传量不足,无法下载");
+        }
+
+        // 应用促销折扣(如果有)
+        double downloadSize = torrentService.calculateDownloadSize(torrentId, userId);
+
+        // 记录下载
+        torrentService.recordDownload(torrentId, userId, downloadSize);
+
+        return ResponseEntity.ok().build();
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/controller/UserController.java b/src/main/java/com/example/myproject/controller/UserController.java
index 4bf6adf..acda403 100644
--- a/src/main/java/com/example/myproject/controller/UserController.java
+++ b/src/main/java/com/example/myproject/controller/UserController.java
@@ -1,16 +1,25 @@
 package com.example.myproject.controller;
 
+import cn.dev33.satoken.annotation.SaCheckLogin;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.example.myproject.common.base.PageUtil;
+import com.example.myproject.dto.param.TorrentParam;
+import com.example.myproject.dto.vo.TorrentVO;
+import com.example.myproject.entity.TorrentEntity;
 import com.example.myproject.mapper.UserMapper;
 import com.example.myproject.mapper.VerificationTokenMapper;
 import com.example.myproject.entity.User;
 import com.example.myproject.entity.VerificationToken;
 import com.example.myproject.service.EmailService;
 import com.example.myproject.service.UserService;
-import com.example.myproject.utils.Result;
+import com.example.myproject.common.base.Result;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -27,6 +36,7 @@
 import javax.annotation.Resource;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
+import java.util.List;
 
 @RestController
 @RequestMapping("/user")
@@ -61,9 +71,9 @@
             User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
 
             System.out.println("Login successful for user: " + username);
-            return Result.success(user);
+            return Result.ok(user);
         } catch (AuthenticationException e) {
-            return Result.error("401", "登录失败:" + e.getMessage());
+            return Result.error("登录失败");
         }
     }
 
@@ -71,15 +81,15 @@
     @ApiOperation(value = "用户注册", notes = "使用用户信息进行注册")
     public Result registerController(@RequestBody @ApiParam(value = "新用户信息", required = true) User newUser) {
         if (userService.checkEmailExists(newUser.getEmail())) {
-            return Result.error("邮箱冲突", "邮箱已被使用,请使用其他邮箱注册或找回密码!");
+            return Result.error( "邮箱已被使用,请使用其他邮箱注册或找回密码!");
         }
         boolean success = userService.preRegisterUser(newUser);
         if (success) {
             User responseUser = new User();
             responseUser.setEmail(newUser.getEmail());
-            return Result.success(responseUser, "验证邮件已发送,请检查您的邮箱。");
+            return Result.ok();
         } else {
-            return Result.error("注册失败", "账号已存在或注册失败!");
+            return Result.error("账号已存在或注册失败!");
         }
     }
 
@@ -100,9 +110,9 @@
         String code = verificationRequest.getCode();
         boolean isVerified = userService.verifyEmail(email, code);
         if (isVerified) {
-            return Result.success(null, "邮箱验证成功!");
+            return Result.ok();
         } else {
-            return Result.error("验证失败", "验证码错误或已过期!");
+            return Result.error( "验证码错误或已过期!");
         }
     }
 
@@ -123,7 +133,7 @@
         if (user == null) {
             logger.error("未找到与该邮箱地址相关联的用户: {}", email);
             return ResponseEntity.status(HttpStatus.BAD_REQUEST)
-                    .body(Result.error("1","未找到与该邮箱地址相关联的用户"));
+                    .body(Result.error("未找到与该邮箱地址相关联的用户"));
         }
 
         // 生成验证码
@@ -139,15 +149,42 @@
         logger.info("验证令牌已保存,用户: {}", user.getUsername());
         emailService.sendVerificationEmail(email, token);
 
-        return ResponseEntity.ok(Result.success(200, "验证邮件已发送!"));
+        return ResponseEntity.ok(Result.ok());
     }
     @PostMapping("/checkPassword")
     public Result<String> checkPassword(@RequestParam Long userId, @RequestParam String password) {
         boolean isPasswordCorrect = userService.checkPassword(userId, password);
         if (isPasswordCorrect) {
-            return Result.success("200","原始密码输入正确");
+            return Result.ok();
         } else {
-            return Result.error("305","原始密码输入错误");
+            return Result.error("原始密码输入错误");
         }
     }
+
+
+//    @SaCheckLogin
+//    @Operation(summary = "用户收藏列表", description = "获取用户收藏的种子列表-分页-排序")
+//    @ApiResponse(responseCode = "0", description = "操作成功",
+//            content = {@Content(mediaType = "application/json",
+//                    schema = @Schema(implementation = TorrentVO.class))
+//            })
+//    @PostMapping("/favorite/list")
+//    public Result listFavorites(@RequestBody FavoriteParam param) {
+//        if (param.getUserId() == null) {
+//            return Result.error("缺少 userId");
+//        }
+//
+//        // 校验排序字段是否合理(可选)
+//        param.validOrder(param.getOrderKey(TorrentEntity.class));
+//
+//        PageUtil.startPage(param);
+//
+//        List<TorrentEntity> list = favoriteService.getUserFavoritesPaged(param.getUserId());
+//
+//        return Result.ok(list, PageUtil.getPage(list));
+//    }
+//
+
+
+
 }
diff --git a/src/main/java/com/example/myproject/dto/PromotionCreateDTO.java b/src/main/java/com/example/myproject/dto/PromotionCreateDTO.java
new file mode 100644
index 0000000..211979b
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/PromotionCreateDTO.java
@@ -0,0 +1,35 @@
+package com.example.myproject.dto;
+
+import lombok.Data;
+import javax.validation.constraints.*;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class PromotionCreateDTO {
+    @NotBlank(message = "促销名称不能为空")
+    @Size(max = 100, message = "促销名称长度不能超过100个字符")
+    private String name;
+    
+    @Size(max = 500, message = "描述长度不能超过500个字符")
+    private String description;
+    
+    @NotNull(message = "开始时间不能为空")
+    private LocalDateTime startTime;
+    
+    @NotNull(message = "结束时间不能为空")
+    private LocalDateTime endTime;
+    
+    @NotNull(message = "折扣比例不能为空")
+    @Min(value = 0, message = "折扣比例不能小于0")
+    @Max(value = 100, message = "折扣比例不能大于100")
+    private double discountPercentage;
+    
+    @NotEmpty(message = "适用种子列表不能为空")
+    private List<Long> applicableTorrentIds;
+    
+    @AssertTrue(message = "结束时间必须晚于开始时间")
+    public boolean isEndTimeAfterStartTime() {
+        return endTime != null && startTime != null && endTime.isAfter(startTime);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/dto/TorrentUpdateDTO.java b/src/main/java/com/example/myproject/dto/TorrentUpdateDTO.java
new file mode 100644
index 0000000..4b09321
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/TorrentUpdateDTO.java
@@ -0,0 +1,24 @@
+package com.example.myproject.dto;
+
+import lombok.Data;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+@Data
+public class TorrentUpdateDTO {
+    @NotBlank(message = "标题不能为空")
+    @Size(max = 30, message = "名称长度不能超过30个字符")
+    private String title;
+    
+    @Size(max = 1000, message = "描述长度不能超过1000个字符")
+    private String description;
+    
+    @NotBlank(message = "分类不能为空")
+    private String category;
+    
+
+    private String tags;
+    @NotBlank(message = "封面不能为空")
+    
+    private String imageUrl;
+} 
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/dto/param/TorrentParam.java b/src/main/java/com/example/myproject/dto/param/TorrentParam.java
new file mode 100644
index 0000000..1ef832b
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/param/TorrentParam.java
@@ -0,0 +1,43 @@
+package com.example.myproject.dto.param;
+
+import com.example.myproject.common.base.OrderPageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * 种子查询参数
+ */
+@Data
+@Schema(description = "种子查询参数")
+public class TorrentParam extends OrderPageParam {
+
+    @Schema(description = "关键字")
+    private String keyword;
+
+    @Schema(description = "分类")
+    private String category;
+
+    @Schema(description = "促销种子")
+    private String free;
+
+    private Set<String> likeExpressions;
+
+    public void buildLike() {
+        likeExpressions = new LinkedHashSet<>();
+        if (StringUtils.isEmpty(keyword)) {
+            return;
+        }
+        keyword = keyword.replace(".", " ");
+        String[] searchstrExploded = keyword.split(" ");
+        for (int i = 0; i < searchstrExploded.length && i < 10; i++) {
+            String searchstrElement = searchstrExploded[i].trim();
+            if (!searchstrElement.isEmpty()) {
+                likeExpressions.add(searchstrElement);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/dto/param/TorrentUploadParam.java b/src/main/java/com/example/myproject/dto/param/TorrentUploadParam.java
new file mode 100644
index 0000000..6ede468
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/param/TorrentUploadParam.java
@@ -0,0 +1,28 @@
+package com.example.myproject.dto.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Schema(description = "种子上传参数")
+public class TorrentUploadParam {
+    @Schema(description = "上传者")
+    private String uploader;
+
+    @Schema(description = "种子标题")
+    private String title;
+
+    @Schema(description = "种子描述")
+    private String description;
+
+    @Schema(description = "种子标签")
+    private String tags;
+
+    @Schema(description = "种子分类")
+    private String category;
+
+    @Schema(description = "种子封面图 URL")
+    private String imageUrl;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/dto/vo/TorrentVO.java b/src/main/java/com/example/myproject/dto/vo/TorrentVO.java
new file mode 100644
index 0000000..e723cb3
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/vo/TorrentVO.java
@@ -0,0 +1,105 @@
+package com.example.myproject.dto.vo;
+
+import java.time.LocalDateTime;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class TorrentVO {
+    /**
+     * 种子ID
+     */
+    @Schema(description = "种子ID")
+    private Long id;
+
+    /**
+     * 标题
+     */
+    @NotEmpty
+    @Schema(description = "标题")
+    private String title;
+
+
+    /**
+     * 封面
+     */
+    @Schema(description = "封面")
+    private String imageUrl;
+    /**
+     * 描述
+     */
+    @NotEmpty
+    @Schema(description = "描述")
+    private String description;
+
+    /**
+     * 类别
+     */
+    @NotNull
+    @Schema(description = "类别")
+    private String category;
+
+
+    /**
+     * 添加日期
+     */
+    @Schema(description = "添加日期")
+    private LocalDateTime createTime;
+
+    /**
+     * 修改日期
+     */
+    @Schema(description = "修改日期")
+    private LocalDateTime updateTime;
+
+    /**
+     * 上传者
+     */
+    @Schema(description = "上传者")
+    private String uploader;
+    /**
+     * 文件大小
+     */
+    @Schema(description = "文件大小")
+    private Long size;
+   
+
+
+    /**
+     * 评论数
+     */
+    @Schema(description = "评论数")
+    private Integer comments;
+    /**
+     * 浏览次数
+     */
+    @Schema(description = "浏览次数")
+    private Integer views;
+    /**
+     * 点击次数
+     */
+    @Schema(description = "点击次数")
+    private Integer hits;
+
+
+    /**
+     * 下载数
+     */
+    @Schema(description = "下载数")
+    private Integer leechers;
+    /**
+     * 做种数
+     */
+    @Schema(description = "做种数")
+    private Integer seeders;
+
+    /**
+     * 完成次数
+     */
+    @Schema(description = "完成次数")
+    private Integer completions;
+
+}
diff --git a/src/main/java/com/example/myproject/entity/EntityBase.java b/src/main/java/com/example/myproject/entity/EntityBase.java
new file mode 100644
index 0000000..48583cb
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/EntityBase.java
@@ -0,0 +1,61 @@
+package com.example.myproject.entity;
+
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.util.Objects;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Base class for an entity, as explained in the book "Domain Driven Design".
+ * All entities in this project have an identity attribute with type Long and
+ * name id. Inspired by the DDD Sample project.
+
+ */
+@Setter
+@Getter
+public abstract class EntityBase {
+
+    /**
+     * This identity field has the wrapper class type Long so that an entity which
+     * has not been saved is recognizable by a null identity.
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    @Override
+    public boolean equals(final Object object) {
+        if (!(object instanceof EntityBase)) {
+            return false;
+        }
+        if (!getClass().equals(object.getClass())) {
+            return false;
+        }
+        final EntityBase that = (EntityBase) object;
+        _checkIdentity(this);
+        _checkIdentity(that);
+        return this.id.equals(that.getId());
+    }
+
+
+    private void _checkIdentity(final EntityBase entity) {
+        if (entity.getId() == null) {
+            throw new IllegalStateException("Comparison identity missing in entity: " + entity);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.getId());
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + "<" + getId() + ">";
+    }
+
+}
diff --git a/src/main/java/com/example/myproject/entity/FavoriteEntity.java b/src/main/java/com/example/myproject/entity/FavoriteEntity.java
new file mode 100644
index 0000000..3f76cfe
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/FavoriteEntity.java
@@ -0,0 +1,36 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@TableName("favorite")
+@ApiModel("收藏实体类")
+public class FavoriteEntity {
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "收藏ID", example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "用户ID", example = "1001")
+    @JsonProperty("userId")
+    @TableField("user_id")
+    private Long userId;
+
+    @ApiModelProperty(value = "种子ID", example = "2001")
+    @JsonProperty("seedId")
+    @TableField("seed_id")
+    private Long seedId;
+
+    @ApiModelProperty(value = "收藏时间", example = "2024-05-13 12:00:00")
+    @JsonProperty("createTime")
+    @TableField("create_time")
+    private Date createTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/entity/Promotion.java b/src/main/java/com/example/myproject/entity/Promotion.java
new file mode 100644
index 0000000..4ca846b
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/Promotion.java
@@ -0,0 +1,34 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@TableName("promotion")
+public class Promotion {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private String name;
+    
+    private String description;
+    
+    private LocalDateTime startTime;
+    
+    private LocalDateTime endTime;
+    
+    private double discountPercentage;
+    
+    private String applicableTorrentIds;
+    
+    private LocalDateTime createTime;
+    
+    private LocalDateTime updateTime;
+    
+    private Boolean isDeleted;
+} 
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/entity/TorrentEntity.java b/src/main/java/com/example/myproject/entity/TorrentEntity.java
new file mode 100644
index 0000000..f62fb76
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/TorrentEntity.java
@@ -0,0 +1,108 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@TableName("torrent")
+@ApiModel("种子实体类")
+public class TorrentEntity {
+
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "种子ID", example = "1")
+    private Long id;
+
+    @JsonProperty("infoHash")
+    @ApiModelProperty(value = "种子信息哈希", example = "abcdef123456")
+    private String infoHash;
+
+    @JsonProperty("fileName")
+    @ApiModelProperty(value = "种子文件名", example = "movie_torrent_file.torrent")
+    private String fileName;
+
+    @JsonProperty("uploader")
+    @ApiModelProperty(value = "上传者", example = "user123")
+    private String uploader;
+
+    @JsonProperty("createdTime")
+    @ApiModelProperty(value = "上传时间", example = "2024-01-01 12:00:00")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @JsonProperty("updateTime")
+    @ApiModelProperty(value = "更新时间", example = "2024-01-01 12:00:00")
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @JsonProperty("size")
+    @ApiModelProperty(value = "种子文件大小", example = "123456")
+    private Long size;
+
+    @JsonProperty("title")
+    @ApiModelProperty(value = "种子标题", example = "《星际穿越》")
+    private String title;
+
+    @JsonProperty("description")
+    @ApiModelProperty(value = "种子描述", example = "这是一部好看的科幻电影")
+    private String description;
+
+    @JsonProperty("tags")
+    @ApiModelProperty(value = "种子标签", example = "[\"科幻\",\"动作\"]")
+    private String tags;
+
+    @JsonProperty("category")
+    @ApiModelProperty(value = "种子分类", example = "movie")
+    private String category;
+
+    @JsonProperty("imageUrl")
+    @ApiModelProperty(value = "种子封面图URL", example = "http://example.com/images/cover.jpg")
+    private String imageUrl;
+
+    @JsonProperty("leechers")
+    @ApiModelProperty(value = "下载次数", example = "123")
+    private Integer leechers;
+
+    @JsonProperty("seeders")
+    @ApiModelProperty(value = "做种数", example = "10")
+    private Integer seeders;
+
+    @JsonProperty("comments")
+    @ApiModelProperty(value = "评论数", example = "5")
+    private Integer comments;
+
+    @JsonProperty("views")
+    @ApiModelProperty(value = "浏览次数", example = "1000")
+    private Integer views;
+
+    @JsonProperty("hits")
+    @ApiModelProperty(value = "点击次数", example = "2000")
+    private Integer hits;
+
+    @JsonProperty("promotionTimeType")
+    @ApiModelProperty(value = "促销时间类型", example = "1")
+    private Integer promotionTimeType;
+
+    @JsonProperty("promotionUntil")
+    @ApiModelProperty(value = "促销截止日期", example = "2024-12-31T23:59:59")
+    private LocalDateTime promotionUntil;
+
+    
+    @JsonProperty("torrentFile")
+    @ApiModelProperty(value = "种子文件", example = "base64 encoded torrent file")
+    private byte[] torrentFile;
+
+    @JsonProperty("isDeleted")
+    @ApiModelProperty(value = "是否删除", example = "false")
+    private Boolean isDeleted;
+
+    public TorrentEntity() {
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/entity/User.java b/src/main/java/com/example/myproject/entity/User.java
index 20f7138..7574e67 100644
--- a/src/main/java/com/example/myproject/entity/User.java
+++ b/src/main/java/com/example/myproject/entity/User.java
@@ -9,6 +9,8 @@
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.time.LocalDateTime;
+
 @Data
 @TableName("user") // 指定数据库表名
 @ApiModel("用户实体类") // 用于描述模型
@@ -50,6 +52,26 @@
     @ApiModelProperty(value = "头像")
     private String avatar;
 
+    @JsonProperty("uploaded")
+    @ApiModelProperty(value = "上传量", example = "1000")
+    private Long uploaded;
+
+    @JsonProperty("downloaded")
+    @ApiModelProperty(value = "下载量", example = "500")
+    private Long downloaded;
+
+    @JsonProperty("create_time")
+    @ApiModelProperty(value = "创建时间", example = "2024-04-01T12:00:00")
+    private LocalDateTime createTime;
+
+    @JsonProperty("update_time")
+    @ApiModelProperty(value = "更新时间", example = "2024-04-01T12:00:00")
+    private LocalDateTime updateTime;
+
+    @JsonProperty("is_deleted")
+    @ApiModelProperty(value = "是否删除", example = "false")
+    private Boolean isDeleted;
+
     public User() {
     }
 }
diff --git a/src/main/java/com/example/myproject/mapper/FavoriteMapper.java b/src/main/java/com/example/myproject/mapper/FavoriteMapper.java
new file mode 100644
index 0000000..08b8151
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/FavoriteMapper.java
@@ -0,0 +1,15 @@
+package com.example.myproject.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.myproject.entity.FavoriteEntity;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface FavoriteMapper extends BaseMapper<FavoriteEntity> {
+
+   @Select("SELECT * FROM favorite WHERE user_id = #{userId} AND seed_id = #{seedId} LIMIT 1")
+    FavoriteEntity selectByUserIdAndSeedId(@Param("userId") Long userId, @Param("seedId") Long seedId);
+
+    @Delete("DELETE FROM favorite WHERE user_id = #{userId} AND seed_id = #{seedId}")
+    void deleteByUserIdAndSeedId(@Param("userId") Long userId, @Param("seedId") Long seedId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/mapper/PromotionMapper.java b/src/main/java/com/example/myproject/mapper/PromotionMapper.java
new file mode 100644
index 0000000..a478835
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/PromotionMapper.java
@@ -0,0 +1,52 @@
+package com.example.myproject.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.myproject.entity.Promotion;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Mapper
+public interface PromotionMapper extends BaseMapper<Promotion> {
+
+    @Select("SELECT * FROM promotion WHERE is_deleted = false " +
+            "AND start_time <= #{now} AND end_time >= #{now}")
+    List<Promotion> findActivePromotions(@Param("now") LocalDateTime now);
+
+    /**
+     * 查找某个torrentId是否在促销活动的applicable_torrent_ids字符串中
+     * 用MySQL的FIND_IN_SET判断
+     */
+    @Select("SELECT p.* FROM promotion p " +
+            "WHERE p.is_deleted = false " +
+            "AND p.start_time <= #{now} AND p.end_time >= #{now} " +
+            "AND FIND_IN_SET(#{torrentId}, p.applicable_torrent_ids) > 0")
+    List<Promotion> findActivePromotionsForTorrent(
+            @Param("torrentId") Long torrentId,
+            @Param("now") LocalDateTime now);
+
+    /**
+     * 校验种子id是否存在,返回计数,0代表不存在,>0代表存在
+     */
+    @Select("SELECT COUNT(*) FROM torrent WHERE id = #{torrentId}")
+    int checkTorrentExists(@Param("torrentId") Long torrentId);
+
+    /**
+     * 插入促销活动
+     */
+//    int insert(Promotion promotion);
+
+    /**
+     * 根据ID更新促销活动(例如软删除)
+     */
+    int updateById(Promotion promotion);
+
+    /**
+     * 根据ID查询促销活动
+     */
+    Promotion selectById(@Param("id") Long id);
+}
diff --git a/src/main/java/com/example/myproject/mapper/TorrentMapper.java b/src/main/java/com/example/myproject/mapper/TorrentMapper.java
new file mode 100644
index 0000000..f9cdcdc
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/TorrentMapper.java
@@ -0,0 +1,32 @@
+package com.example.myproject.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.myproject.dto.param.TorrentParam;
+import com.example.myproject.entity.TorrentEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.util.List;
+
+@Mapper
+public interface TorrentMapper extends BaseMapper<TorrentEntity> {
+
+    @Select("SELECT * FROM torrents WHERE info_hash = #{infoHash}")
+    TorrentEntity selectByInfoHash(String infoHash);
+    
+    @Select("SELECT * FROM torrents WHERE seed_id = #{seedId}")
+    TorrentEntity selectBySeedId(Long seedId);
+    
+    List<TorrentEntity> search(@Param("param") TorrentParam param);
+    
+    @Update("UPDATE torrent SET downloads = downloads + 1 WHERE id = #{torrentId}")
+    void increaseDownloads(@Param("torrentId") Long torrentId);
+    
+    boolean checkFavorite(@Param("seedId") Long seedId, @Param("userId") Long userId);
+    
+    void addFavorite(@Param("seedId") Long seedId, @Param("userId") Long userId);
+    
+    void removeFavorite(@Param("seedId") Long seedId, @Param("userId") Long userId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/mapper/UserMapper.java b/src/main/java/com/example/myproject/mapper/UserMapper.java
index a070bb5..3a29b64 100644
--- a/src/main/java/com/example/myproject/mapper/UserMapper.java
+++ b/src/main/java/com/example/myproject/mapper/UserMapper.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 import org.springframework.data.repository.query.Param;
 
 import java.util.List;
@@ -22,4 +23,9 @@
     // 根据用户名包含查找用户
     List<User> selectByUsernameContaining(@Param("name") String name);
 
+    @Update("UPDATE user SET downloaded = downloaded + #{size} WHERE id = #{userId}")
+    void increaseDownloaded(@Param("userId") Long userId, @Param("size") double size);
+    
+    boolean hasRole(@Param("userId") Long userId, @Param("role") String role);
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/PromotionService.java b/src/main/java/com/example/myproject/service/PromotionService.java
new file mode 100644
index 0000000..087c13b
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/PromotionService.java
@@ -0,0 +1,17 @@
+package com.example.myproject.service;
+
+import com.example.myproject.entity.Promotion;
+import com.example.myproject.dto.PromotionCreateDTO;
+import java.util.List;
+
+public interface PromotionService {
+    Promotion createPromotion(PromotionCreateDTO promotionDTO);
+    
+    List<Promotion> getAllActivePromotions();
+    
+    Promotion getPromotionById(Long promotionId);
+    
+    void deletePromotion(Long promotionId);
+    
+    double getCurrentDiscount(Long torrentId);
+} 
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/TorrentService.java b/src/main/java/com/example/myproject/service/TorrentService.java
new file mode 100644
index 0000000..941804d
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/TorrentService.java
@@ -0,0 +1,37 @@
+package com.example.myproject.service;
+
+import com.example.myproject.common.base.Result;
+import com.example.myproject.entity.TorrentEntity;
+import com.example.myproject.dto.param.TorrentParam;
+import com.example.myproject.dto.param.TorrentUploadParam;
+import com.example.myproject.dto.TorrentUpdateDTO;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface TorrentService {
+    List<TorrentEntity> search(TorrentParam param);
+    
+    TorrentEntity selectBySeedId(Long seedId);
+    
+    void uploadTorrent(MultipartFile file, TorrentUploadParam param) throws IOException;
+    
+    byte[] fetch(Long seedId, String passkey) throws IOException;
+    
+    Result favorite(Long seedId, Long userId);
+    
+    void deleteTorrent(Long seedId);
+    
+    void updateTorrent(Long seedId, TorrentUpdateDTO updateDTO);
+    
+    boolean canUserDeleteTorrent(Long seedId, Long userId);
+    
+    boolean canUserUpdateTorrent(Long seedId, Long userId);
+    
+    boolean checkUserUploadRatio(Long userId);
+    
+    double calculateDownloadSize(Long torrentId, Long userId);
+    
+    void recordDownload(Long torrentId, Long userId, double downloadSize);
+}
diff --git a/src/main/java/com/example/myproject/service/UserService.java b/src/main/java/com/example/myproject/service/UserService.java
index 535f635..71435c7 100644
--- a/src/main/java/com/example/myproject/service/UserService.java
+++ b/src/main/java/com/example/myproject/service/UserService.java
@@ -21,4 +21,5 @@
     boolean checkPassword(Long userId, String rawPassword);
 
 
+//    Integer getUserId();
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
new file mode 100644
index 0000000..9d34cbc
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
@@ -0,0 +1,114 @@
+package com.example.myproject.service.serviceImpl;
+
+import com.example.myproject.entity.Promotion;
+import com.example.myproject.mapper.PromotionMapper;
+import com.example.myproject.service.PromotionService;
+import com.example.myproject.dto.PromotionCreateDTO;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class PromotionServiceImpl implements PromotionService {
+
+    @Autowired
+    private PromotionMapper promotionMapper;
+
+    @Override
+    @Transactional
+    public Promotion createPromotion(PromotionCreateDTO promotionDTO) {
+        // 验证时间
+        LocalDateTime now = LocalDateTime.now();
+        if (promotionDTO.getEndTime().isBefore(now)) {
+            throw new RuntimeException("结束时间不能早于当前时间");
+        }
+
+        // 验证种子ID是否存在
+        validateTorrentIds(promotionDTO.getApplicableTorrentIds());
+
+        // 创建促销活动
+        Promotion promotion = new Promotion();
+        promotion.setName(promotionDTO.getName());
+        promotion.setDescription(promotionDTO.getDescription());
+        promotion.setStartTime(promotionDTO.getStartTime());
+        promotion.setEndTime(promotionDTO.getEndTime());
+        promotion.setDiscountPercentage(promotionDTO.getDiscountPercentage());
+
+        // 把List<Long>转换成逗号分隔字符串
+        String applicableTorrentIdsStr = promotionDTO.getApplicableTorrentIds().stream()
+                .map(String::valueOf)
+                .collect(Collectors.joining(","));
+        promotion.setApplicableTorrentIds(applicableTorrentIdsStr);
+
+        promotion.setCreateTime(now);
+        promotion.setUpdateTime(now);
+        promotion.setIsDeleted(false);
+
+        promotionMapper.insert(promotion);
+        return promotion;
+    }
+
+    @Override
+    public List<Promotion> getAllActivePromotions() {
+        LocalDateTime now = LocalDateTime.now();
+        return promotionMapper.findActivePromotions(now);
+    }
+
+    @Override
+    public Promotion getPromotionById(Long promotionId) {
+        Promotion promotion = promotionMapper.selectById(promotionId);
+        if (promotion == null || promotion.getIsDeleted()) {
+            return null;
+        }
+        return promotion;
+    }
+
+    @Override
+    @Transactional
+    public void deletePromotion(Long promotionId) {
+        Promotion promotion = getPromotionById(promotionId);
+        if (promotion == null) {
+            throw new RuntimeException("促销活动不存在");
+        }
+
+        // 软删除
+        promotion.setIsDeleted(true);
+        promotion.setUpdateTime(LocalDateTime.now());
+        promotionMapper.updateById(promotion);
+    }
+
+    @Override
+    public double getCurrentDiscount(Long torrentId) {
+        LocalDateTime now = LocalDateTime.now();
+        List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
+
+        // 如果有多个促销活动,取折扣最大的
+        return activePromotions.stream()
+                .mapToDouble(Promotion::getDiscountPercentage)
+                .max()
+                .orElse(0.0);
+    }
+
+    /**
+     * 验证种子ID是否存在
+     */
+    private void validateTorrentIds(List<Long> torrentIds) {
+        if (torrentIds == null || torrentIds.isEmpty()) {
+            throw new RuntimeException("适用种子列表不能为空");
+        }
+
+        // 检查所有种子ID是否都存在
+        List<Long> invalidIds = torrentIds.stream()
+                .filter(id -> promotionMapper.checkTorrentExists(id) == 0)  // 改成 == 0
+                .collect(Collectors.toList());
+
+        if (!invalidIds.isEmpty()) {
+            throw new RuntimeException("以下种子ID不存在: " + invalidIds);
+        }
+    }
+}
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
new file mode 100644
index 0000000..884c65d
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
@@ -0,0 +1,296 @@
+package com.example.myproject.service.serviceImpl;
+
+import com.example.myproject.entity.TorrentEntity;
+import com.example.myproject.entity.User;
+import com.example.myproject.mapper.TorrentMapper;
+import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.service.TorrentService;
+import com.example.myproject.service.PromotionService;
+import com.example.myproject.dto.param.TorrentParam;
+import com.example.myproject.dto.param.TorrentUploadParam;
+import com.example.myproject.dto.TorrentUpdateDTO;
+import com.turn.ttorrent.bcodec.BDecoder;
+import com.turn.ttorrent.bcodec.BEValue;
+import com.turn.ttorrent.bcodec.BEncoder;
+import com.turn.ttorrent.client.SimpleClient;
+import com.turn.ttorrent.common.creation.MetadataBuilder;
+import com.turn.ttorrent.tracker.Tracker;
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.example.myproject.common.base.Result;
+
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import org.apache.commons.codec.binary.Hex;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@Service
+public class TorrentServiceImpl implements TorrentService {
+    @Autowired
+    private Tracker tracker;
+
+    @Autowired
+    private TorrentMapper torrentMapper;
+
+    private final Map<String, TrackedTorrent> torrentRegistry = new HashMap<>();
+
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private PromotionService promotionService;
+
+    private static final double MIN_UPLOAD_RATIO = 0.5; // 最小上传比例要求
+
+    @Override
+    public List<TorrentEntity> search(TorrentParam param) {
+        return torrentMapper.search(param);
+    }
+
+    @Override
+    public TorrentEntity selectBySeedId(Long seedId) {
+        return torrentMapper.selectById(seedId);
+    }
+    private final ExecutorService seederExecutor = Executors.newCachedThreadPool();
+
+    @Override
+    @Transactional
+    public void uploadTorrent(MultipartFile file, TorrentUploadParam param) throws IOException {
+        // 验证用户权限
+        User user = userMapper.selectById(param.getUploader());
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        String workingDir = System.getProperty("user.dir");
+        Path originalDir = Paths.get(workingDir, "data", "files");
+        Files.createDirectories(originalDir);
+        Path originalFilePath = originalDir.resolve(file.getOriginalFilename());
+        Files.copy(file.getInputStream(), originalFilePath, StandardCopyOption.REPLACE_EXISTING);
+
+        MetadataBuilder builder = new MetadataBuilder()
+                .addFile(originalFilePath.toFile(), file.getOriginalFilename()) // 添加原始文件
+                .setTracker(" ") // 设置Tracker地址
+                .setPieceLength(512 * 1024) // 分片大小512KB
+                .setComment("Generated by PT站")
+                .setCreatedBy("PT-Server");
+
+        // 处理种子文件
+        byte[] torrentBytes = file.getBytes();
+        String infoHash = null;
+        try {
+            infoHash = calculateInfoHash(torrentBytes);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+
+        // 保存种子文件到data/torrents目录
+        Path torrentDir = Paths.get(workingDir, "data", "torrents");
+        Files.createDirectories(torrentDir);
+        Path torrentPath = torrentDir.resolve(infoHash + ".torrent");
+        Files.copy(new ByteArrayInputStream(torrentBytes), torrentPath, StandardCopyOption.REPLACE_EXISTING);
+
+        // 注册到Tracker
+        TrackedTorrent torrent = TrackedTorrent.load(torrentPath.toFile());
+        tracker.announce(torrent);
+
+
+        // 异步启动做种客户端
+        seederExecutor.submit(() -> {
+            try {
+                startSeeding(torrentPath, originalDir);
+            } catch (Exception e) {
+                Result.error("做种失败: " + e.getMessage());
+            }
+        });
+
+
+
+
+        // 保存种子信息
+        TorrentEntity entity= new TorrentEntity();
+        entity.setUploader(param.getUploader());
+        entity.setFileName(file.getOriginalFilename());
+        entity.setSize(file.getSize());
+        entity.setCategory(param.getCategory());
+        entity.setTags(param.getTags());
+        entity.setTitle(param.getTitle());
+        entity.setImageUrl(param.getImageUrl());
+        entity.setTorrentFile(torrentBytes);
+        entity.setInfoHash(infoHash);
+
+        torrentMapper.insert(entity);
+    }
+
+    @Override
+    public byte[] fetch(Long seedId, String passkey) {
+        TorrentEntity torrent = selectBySeedId(seedId);
+        if (torrent == null) {
+            throw new RuntimeException("种子不存在");
+        }
+
+        byte[] torrentBytes = torrent.getTorrentFile();
+
+        try {
+            // 1. 解码 .torrent 文件为 Map
+            Map<String, BEValue> decoded = BDecoder.bdecode(new ByteArrayInputStream(torrentBytes)).getMap();
+
+            // 2. 获取原始 announce 字段
+            String announce = decoded.get("announce").getString();
+
+            // 3. 注入 passkey 到 announce URL
+            if (!announce.contains("passkey=")) {
+                announce = announce + "?passkey=" + passkey;
+                decoded.put("announce", new BEValue(announce));
+            }
+
+            // 4. 编码成新的 .torrent 文件字节数组
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            BEncoder.bencode(decoded, out);
+            return out.toByteArray();
+
+        } catch (Exception e) {
+            throw new RuntimeException("处理 torrent 文件失败", e);
+        }
+    }
+
+    @Override
+    @Transactional
+    public Result favorite(Long seedId, Long userId) {
+        try {
+            boolean exists = torrentMapper.checkFavorite(seedId, userId);
+            if (exists) {
+                torrentMapper.removeFavorite(seedId, userId);
+                return Result.success("取消收藏成功");
+            } else {
+                torrentMapper.addFavorite(seedId, userId);
+                return Result.success("收藏成功");
+            }
+        } catch (Exception e) {
+            return Result.error("失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional
+    public void deleteTorrent(Long seedId) {
+        torrentMapper.deleteById(seedId);
+    }
+
+    @Override
+    @Transactional
+    public void updateTorrent(Long seedId, TorrentUpdateDTO updateDTO) {
+        TorrentEntity torrent = selectBySeedId(seedId);
+        if (torrent == null) {
+            throw new RuntimeException("种子不存在");
+        }
+
+
+        torrent.setDescription(updateDTO.getDescription());
+        torrent.setCategory(updateDTO.getCategory());
+        torrent.setTitle(updateDTO.getTitle());
+        torrent.setTags(updateDTO.getTags());
+        torrent.setImageUrl(updateDTO.getImageUrl());
+
+        torrentMapper.updateById(torrent);
+    }
+
+    @Override
+    public boolean canUserDeleteTorrent(Long seedId, Long userId) {
+        TorrentEntity torrent = selectBySeedId(seedId);
+        if (torrent == null) {
+            return false;
+        }
+
+        // 检查是否是种子发布者或管理员
+        return torrent.getUploader().equals(userId) ||
+                userMapper.hasRole(userId, "admin");
+    }
+
+    @Override
+    public boolean canUserUpdateTorrent(Long seedId, Long userId) {
+        return canUserDeleteTorrent(seedId, userId);
+    }
+
+    @Override
+    public boolean checkUserUploadRatio(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return false;
+        }
+
+        // 防止除以零
+        if (user.getDownloaded() == 0) {
+            return true;
+        }
+
+        double uploadRatio = user.getUploaded() / (double) user.getDownloaded();
+        return uploadRatio >= MIN_UPLOAD_RATIO;
+    }
+    /**
+     * 启动做种客户端
+     */
+    private void startSeeding(Path torrentPath, Path dataDir) throws Exception {
+        SimpleClient seederClient = new SimpleClient();
+        seederClient.downloadTorrent(
+                torrentPath.toString(),
+                dataDir.toString(),
+                InetAddress.getLocalHost());
+        // 保持做种状态(阻塞线程)
+        while (true) {
+            Thread.sleep(60000); // 每60秒检查一次
+        }
+    }
+
+
+    @Override
+    public double calculateDownloadSize(Long torrentId, Long userId) {
+        TorrentEntity torrent = selectBySeedId(torrentId);
+        if (torrent == null) {
+            throw new RuntimeException("种子不存在");
+        }
+
+        // 获取当前有效的促销活动
+        double discount = promotionService.getCurrentDiscount(torrentId);
+
+        // 计算实际下载量
+        return torrent.getSize() * (1 - discount / 100.0);
+    }
+
+    @Override
+    @Transactional
+    public void recordDownload(Long torrentId, Long userId, double downloadSize) {
+        // 更新用户下载量
+        userMapper.increaseDownloaded(userId, downloadSize);
+
+        // 更新种子下载次数
+        torrentMapper.increaseDownloads(torrentId);
+    }
+    /**
+     * 计算种子文件的infoHash
+     */
+    private String calculateInfoHash(byte[] torrentData) throws NoSuchAlgorithmException  {
+        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+        sha1.update(torrentData);
+        byte[] hashBytes = sha1.digest();
+        return Hex.encodeHexString(hashBytes);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/utils/Result.java b/src/main/java/com/example/myproject/utils/Result.java
deleted file mode 100644
index e838a1c..0000000
--- a/src/main/java/com/example/myproject/utils/Result.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.example.myproject.utils;
-
-
-public class Result<T> {
-    private String code;
-    private String msg;
-    private T data;
-
-    public String getCode() {
-        return code;
-    }
-
-    public void setCode(String code) {
-        this.code = code;
-    }
-
-    public String getMsg() {
-        return msg;
-    }
-
-    public void setMsg(String msg) {
-        this.msg = msg;
-    }
-
-    public T getData() {
-        return data;
-    }
-
-    public void setData(T data) {
-        this.data = data;
-    }
-
-    public Result() {
-    }
-
-    public Result(T data) {
-        this.data = data;
-    }
-
-    public static Result success() {
-        Result result = new Result<>();
-        result.setCode("200");
-        result.setMsg("成功");
-        return result;
-    }
-
-    public static <T> Result<T> success(T data) {
-        Result<T> result = new Result<>(data);
-        result.setCode("200");
-        result.setMsg("成功");
-        return result;
-    }
-    public static <T> Result<T> success(String msg) {
-        Result result = new Result();
-        result.setCode("200");
-        result.setMsg("成功");
-        return result;
-    }
-
-    public static <T> Result<T> success(T data,String msg) {
-        Result<T> result = new Result<>(data);
-        result.setCode("200");
-        result.setMsg(msg);
-        return result;
-    }
-
-    public static Result error(String code, String msg) {
-        Result result = new Result();
-        result.setCode(code);
-        result.setMsg(msg);
-        return result;
-    }
-    /**
-     * 创建一个表示文件大小超出限制的结果对象。
-     *
-     * @return 构造的文件过大错误结果对象
-     */
-    public static Result fileTooLarge() {
-        Result result = new Result();
-        result.setCode("413");
-        result.setMsg("文件大小超出限制。");
-        return result;
-    }
-
-    /**
-     * 创建一个表示文件格式不支持的结果对象。
-     *
-     * @return 构造的文件格式错误结果对象
-     */
-    public static Result unsupportedFileType() {
-        Result result = new Result();
-        result.setCode("415");
-        result.setMsg("不支持的文件格式。");
-        return result;
-    }
-
-    /**
-     * 创建一个表示文件未找到的结果对象。
-     *
-     * @return 构造的文件未找到错误结果对象
-     */
-    public static Result fileNotFound() {
-        Result result = new Result();
-        result.setCode("404");
-        result.setMsg("文件未找到。");
-        return result;
-    }
-
-    /**
-     * 创建一个表示文件存储错误的结果对象。
-     *
-     * @param errorMsg 详细错误信息
-     * @return 构造的文件存储错误结果对象
-     */
-    public static Result fileStorageError(String errorMsg) {
-        Result result = new Result();
-        result.setCode("500");
-        result.setMsg("文件存储错误: " + errorMsg);
-        return result;
-    }
-
-    public static Result permissionDenied() {
-        Result result = new Result();
-        result.setCode("401");
-        result.setMsg("权限不足,无法执行该操作。");
-        return result;
-    }
-
-}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index b753da7..0590333 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -19,3 +19,13 @@
 spring.jpa.enabled=false
 spring.jpa.hibernate.ddl-auto=none
 spring.jpa.open-in-view=false
+
+# tracker??
+pt.tracker.port=6969
+
+pt.tracker.torrent-dir=${user.dir}/data/torrents
+
+pt.tracker.allow-foreign=false
+pt.tracker.announce-url=/custom-announce
+
+mybatis-plus.mapper-locations=classpath:/mapper/**/*.xml
diff --git a/src/main/resources/files/files.torrent b/src/main/resources/files/files.torrent
new file mode 100644
index 0000000..e04974f
--- /dev/null
+++ b/src/main/resources/files/files.torrent
@@ -0,0 +1 @@
+d8:announce22:https://tracker.byr.pt10:created by21:qBittorrent v4.5.3.1013:creation datei1747717901e4:infod5:filesld6:lengthi173e4:pathl13:valid.torrenteee4:name5:files12:piece lengthi16384e6:pieces20:/ñíèEô5ã<òûìՄŸQ¡–ûee
\ No newline at end of file
diff --git a/src/main/resources/mapper/FavoriteMapper.xml b/src/main/resources/mapper/FavoriteMapper.xml
new file mode 100644
index 0000000..1048ec2
--- /dev/null
+++ b/src/main/resources/mapper/FavoriteMapper.xml
@@ -0,0 +1,6 @@
+<?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.example.myproject.mapper.FavoriteMapper">
+ 
+</mapper>
diff --git a/src/main/resources/mapper/PromotionMapper.xml b/src/main/resources/mapper/PromotionMapper.xml
new file mode 100644
index 0000000..72ffd95
--- /dev/null
+++ b/src/main/resources/mapper/PromotionMapper.xml
@@ -0,0 +1,14 @@
+<?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.example.myproject.mapper.PromotionMapper">
+
+    <insert id="insert" parameterType="com.example.myproject.entity.Promotion">
+        INSERT INTO promotion (
+        name, description, start_time, end_time, discount_percentage, applicable_torrent_ids
+        ) VALUES (
+        #{name}, #{description}, #{startTime}, #{endTime}, #{discountPercentage}, #{applicableTorrentIds}
+        )
+    </insert>
+
+</mapper>
diff --git a/src/main/resources/mapper/TorrentMapper.xml b/src/main/resources/mapper/TorrentMapper.xml
new file mode 100644
index 0000000..d5f018e
--- /dev/null
+++ b/src/main/resources/mapper/TorrentMapper.xml
@@ -0,0 +1,104 @@
+<?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.example.myproject.mapper.TorrentMapper">
+    <resultMap id="BaseResultMap" type="com.example.myproject.entity.TorrentEntity">
+        <id column="info_hash" property="infoHash"/>
+        <result column="file_name" property="fileName"/>
+        <result column="uploader" property="uploader"/>
+        <result column="upload_time" property="uploadTime"/>
+        <result column="size" property="size"/>
+        <result column="title" property="title"/>
+        <result column="description" property="description"/>
+        <result column="category" property="category"/>
+        <result column="image_url" property="imageUrl"/>
+    </resultMap>
+
+    <select id="selectByInfoHash" resultMap="BaseResultMap">
+        SELECT * FROM torrent WHERE info_hash = #{infoHash}
+    </select>
+    <select id="selectBySeedId" resultMap="BaseResultMap">
+        SELECT * FROM torrent WHERE seed_id = #{seedId}
+    </select>
+
+
+
+
+    <update id="update" parameterType="com.example.myproject.entity.TorrentEntity">
+        UPDATE torrent
+        SET file_name = #{fileName},
+        uploader = #{uploader},
+        upload_time = #{uploadTime},
+        size = #{size},
+        title = #{title},
+        description = #{description},
+        category = #{category},
+        image_url = #{imageUrl}
+        WHERE info_hash = #{infoHash}
+    </update>
+    <select id="search" resultType="com.example.myproject.entity.TorrentEntity">
+        SELECT * FROM torrent
+        <where>
+            <if test="param.category != null">
+                AND category = #{param.category}
+            </if>
+
+            <!--        <if test="param.free != null and param.free != ''">-->
+            <!--            AND free = #{param.free}-->
+            <!--        </if>-->
+            <if test="param.free != null">
+            <choose>
+                <!-- 筛选“正在促销中”的种子 -->
+                <when test="param.free == true">
+                    AND EXISTS (
+                    SELECT 1 FROM promotion p
+                    WHERE
+                    JSON_CONTAINS(p.applicable_torrent_ids, JSON_ARRAY(t.id))
+                    AND NOW() BETWEEN p.start_time AND p.end_time
+                    AND p.is_deleted = 0
+                    )
+                </when>
+                <!-- 筛选“未在促销中”的种子 -->
+                <otherwise>
+                    AND NOT EXISTS (
+                    SELECT 1 FROM promotion p
+                    WHERE
+                    JSON_CONTAINS(p.applicable_torrent_ids, JSON_ARRAY(t.id))
+                    AND NOW() BETWEEN p.start_time AND p.end_time
+                    AND p.is_deleted = 0
+                    )
+                </otherwise>
+            </choose>
+            </if>
+
+            <if test="param.likeExpressions != null and param.likeExpressions.size > 0">
+                AND (
+                <foreach collection="param.likeExpressions" item="item" open="(" separator=" AND " close=")">
+
+                    ( title LIKE CONCAT('%', #{item}, '%') ) or ( description LIKE CONCAT('%', #{item}, '%') ) or ( tags LIKE CONCAT('%', #{item}, '%') )
+                </foreach>
+                )
+            </if>
+        </where>
+
+        <if test="param.prop != null and param.sort != null">
+            ORDER BY ${param.prop} ${param.sort}
+        </if>
+    </select>
+    <select id="checkFavorite" resultType="boolean">
+        SELECT COUNT(*) > 0
+        FROM favorite
+        WHERE seed_id = #{seedId} AND user_id = #{userId}
+    </select>
+    <insert id="addFavorite">
+        INSERT INTO favorite (seed_id, user_id)
+        VALUES (#{seedId}, #{userId})
+    </insert>
+    <delete id="removeFavorite">
+        DELETE FROM favorite
+        WHERE seed_id = #{seedId} AND user_id = #{userId}
+    </delete>
+
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/output/valid.torrent b/src/main/resources/output/valid.torrent
new file mode 100644
index 0000000..6a90e52
--- /dev/null
+++ b/src/main/resources/output/valid.torrent
@@ -0,0 +1 @@
+d10:created by18:qBittorrent v5.1.013:creation datei1745948995e4:infod6:lengthi22e4:name15:example.torrent12:piece lengthi16384e6:pieces20:Fnð¶)ú<Ç	ŽæÂh£tl7:privatei1eee
\ No newline at end of file
diff --git a/src/test/java/com/example/myproject/controller/TorrentControllerTest.java b/src/test/java/com/example/myproject/controller/TorrentControllerTest.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/java/com/example/myproject/controller/TorrentControllerTest.java
diff --git a/src/test/java/com/example/myproject/controller/UserControllerTest.java b/src/test/java/com/example/myproject/controller/UserControllerTest.java
index 8a09e53..d332f31 100644
--- a/src/test/java/com/example/myproject/controller/UserControllerTest.java
+++ b/src/test/java/com/example/myproject/controller/UserControllerTest.java
@@ -6,13 +6,12 @@
 import com.example.myproject.service.UserService;
 import com.example.myproject.mapper.UserMapper;
 import com.example.myproject.mapper.VerificationTokenMapper;
-import com.example.myproject.utils.Result;
+import com.example.myproject.common.base.Result;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.*;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;