Merge "添加依赖"
diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
index bd22aef..054b2d0 100644
--- a/.idea/dbnavigator.xml
+++ b/.idea/dbnavigator.xml
@@ -523,4 +523,7 @@
</environment>
</general-settings>
</component>
+ <component name="DBNavigator.Project.StatementExecutionManager">
+ <execution-variables />
+ </component>
</project>
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 0d5980a..ae3ef3f 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,7 +4,40 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
- <list default="true" id="f008fe30-0711-42e2-bb33-17dcfdbad387" name="Changes" comment="" />
+ <list default="true" id="f008fe30-0711-42e2-bb33-17dcfdbad387" name="Changes" comment="">
+ <change beforePath="$PROJECT_DIR$/.idea/dbnavigator.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/dbnavigator.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/create.sql" beforeDir="false" />
+ <change beforePath="$PROJECT_DIR$/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/MyProjectApplication.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/MyProjectApplication.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/common/base/Result.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/common/base/Result.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/config/MyMetaObjectHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/config/MyMetaObjectHandler.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/InviteController.java" beforeDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/UserController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/UserController.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/dto/param/InviteParam.java" beforeDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/dto/param/TorrentUploadParam.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/dto/param/TorrentUploadParam.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/entity/EntityBase.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/entity/EntityBase.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/entity/InvitationEntity.java" beforeDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/entity/User.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/entity/User.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/InvitationMapper.java" beforeDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/UserMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/UserMapper.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/InvitationService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/InvitationService.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/UserService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/UserService.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/InvitationServiceImpl.java" beforeDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/resources/application.properties" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/application.properties" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/resources/mapper/PromotionMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/mapper/PromotionMapper.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/test/java/com/example/myproject/controller/TorrentControllerTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/java/com/example/myproject/controller/TorrentControllerTest.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/test/java/com/example/myproject/controller/UserControllerTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/java/com/example/myproject/controller/UserControllerTest.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/application.properties" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/application.properties" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/MyProjectApplication.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/MyProjectApplication.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/UserController$EmailRequest.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/UserController$EmailRequest.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/UserController$VerificationRequest.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/UserController$VerificationRequest.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/UserController.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/UserController.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/User.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/User.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/mapper/UserMapper.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/mapper/UserMapper.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/utils/Result.class" beforeDir="false" />
+ </list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -30,23 +63,102 @@
</MavenImportingSettings>
</option>
</component>
+ <component name="ProblemsViewState">
+ <option name="selectedTabId" value="CurrentFile" />
+ </component>
<component name="ProjectId" id="2vZNfNTEFyHApdxmHZ7Y0rlJjKB" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
- <component name="PropertiesComponent"><![CDATA[{
- "keyToString": {
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true"
+ <component name="PropertiesComponent">{
+ "keyToString": {
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "last_opened_file_path": "D:/study/学习资源/大三下/zy1"
}
-}]]></component>
+}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\study\学习资源\大三下\school\echo-backend\src\main\resources\mapper" />
</key>
</component>
+ <component name="RunManager" selected="应用程序.MyProjectApplication">
+ <configuration name="BtClient" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
+ <option name="MAIN_CLASS_NAME" value="com.example.myproject.client.BtClient" />
+ <module name="echo-backend" />
+ <extension name="coverage">
+ <pattern>
+ <option name="PATTERN" value="com.example.myproject.client.*" />
+ <option name="ENABLED" value="true" />
+ </pattern>
+ </extension>
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="MyProjectApplication" type="Application" factoryName="Application" temporary="true">
+ <option name="MAIN_CLASS_NAME" value="com.example.myproject.MyProjectApplication" />
+ <module name="echo-backend" />
+ <extension name="coverage">
+ <pattern>
+ <option name="PATTERN" value="com.example.myproject.*" />
+ <option name="ENABLED" value="true" />
+ </pattern>
+ </extension>
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="MyProjectApplication" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
+ <option name="MAIN_CLASS_NAME" value="com.example.myproject.MyProjectApplication" />
+ <module name="echo-backend" />
+ <extension name="coverage">
+ <pattern>
+ <option name="PATTERN" value="com.example.myproject.*" />
+ <option name="ENABLED" value="true" />
+ </pattern>
+ </extension>
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="TrackerServer" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
+ <option name="MAIN_CLASS_NAME" value="com.example.myproject.tracker.TrackerServer" />
+ <module name="echo-backend" />
+ <extension name="coverage">
+ <pattern>
+ <option name="PATTERN" value="com.example.myproject.tracker.*" />
+ <option name="ENABLED" value="true" />
+ </pattern>
+ </extension>
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="TorrentControllerTest" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
+ <module name="echo-backend" />
+ <extension name="coverage">
+ <pattern>
+ <option name="PATTERN" value="com.example.myproject.controller.*" />
+ <option name="ENABLED" value="true" />
+ </pattern>
+ </extension>
+ <option name="PACKAGE_NAME" value="com.example.myproject.controller" />
+ <option name="MAIN_CLASS_NAME" value="com.example.myproject.controller.TorrentControllerTest" />
+ <option name="TEST_OBJECT" value="class" />
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <recent_temporary>
+ <list>
+ <item itemvalue="应用程序.MyProjectApplication" />
+ <item itemvalue="JUnit.TorrentControllerTest" />
+ </list>
+ </recent_temporary>
+ </component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..06da2ea
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,14 @@
+{
+ "configurations": [
+ {
+ "type": "java",
+ "name": "Spring Boot-MyProjectApplication<echo-backend>",
+ "request": "launch",
+ "cwd": "${workspaceFolder}",
+ "mainClass": "com.example.myproject.MyProjectApplication",
+ "projectName": "echo-backend",
+ "args": "",
+ "envFile": "${workspaceFolder}/.env"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..b84f89c
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive",
+ "java.compile.nullAnalysis.mode": "automatic"
+}
\ No newline at end of file
diff --git a/data/torrents/a4cd16b26a89ec7e5dc534040edca948f78918ec.torrent b/data/torrents/a4cd16b26a89ec7e5dc534040edca948f78918ec.torrent
new file mode 100644
index 0000000..17908b6
--- /dev/null
+++ b/data/torrents/a4cd16b26a89ec7e5dc534040edca948f78918ec.torrent
Binary files differ
diff --git a/data/torrents/files.torrent b/data/torrents/files.torrent
new file mode 100644
index 0000000..e04974f
--- /dev/null
+++ b/data/torrents/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/data/torrents/valid.torrent b/data/torrents/valid.torrent
new file mode 100644
index 0000000..6a90e52
--- /dev/null
+++ b/data/torrents/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/libs/ttorrent-bencoding-1.3.0-SNAPSHOT.jar b/libs/ttorrent-bencoding-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..fcc7fc6
--- /dev/null
+++ b/libs/ttorrent-bencoding-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/libs/ttorrent-cli-1.3.0-SNAPSHOT.jar b/libs/ttorrent-cli-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..4351f7d
--- /dev/null
+++ b/libs/ttorrent-cli-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/libs/ttorrent-client-1.3.0-SNAPSHOT.jar b/libs/ttorrent-client-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..0a3d31f
--- /dev/null
+++ b/libs/ttorrent-client-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/libs/ttorrent-common-1.3.0-SNAPSHOT.jar b/libs/ttorrent-common-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..7d40e7a
--- /dev/null
+++ b/libs/ttorrent-common-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/libs/ttorrent-network-1.0.jar b/libs/ttorrent-network-1.0.jar
new file mode 100644
index 0000000..b851181
--- /dev/null
+++ b/libs/ttorrent-network-1.0.jar
Binary files differ
diff --git a/libs/ttorrent-test-api-1.0.jar b/libs/ttorrent-test-api-1.0.jar
new file mode 100644
index 0000000..0c4d56a
--- /dev/null
+++ b/libs/ttorrent-test-api-1.0.jar
Binary files differ
diff --git a/libs/ttorrent-tests-1.3.0-SNAPSHOT.jar b/libs/ttorrent-tests-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..577bfb7
--- /dev/null
+++ b/libs/ttorrent-tests-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/libs/ttorrent-tracker-1.3.0-SNAPSHOT.jar b/libs/ttorrent-tracker-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..5f70461
--- /dev/null
+++ b/libs/ttorrent-tracker-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/pom.xml b/pom.xml
index ef0fb95..20b8e71 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
@@ -11,13 +11,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version> <!-- 使用你希望的Spring Boot版本 -->
- <relativePath/> <!-- lookup parent from repository -->
+ <relativePath /> <!-- lookup parent from repository -->
</parent>
-
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>
@@ -78,6 +78,16 @@
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.github.xiaoymin</groupId>
+ <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
+ <version>4.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>2.0.1.Final</version>
+ </dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
@@ -108,11 +118,17 @@
<version>1.18.30</version> <!-- 确保使用的是最新版本 -->
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.github.pagehelper</groupId>
+ <artifactId>pagehelper-spring-boot-starter</artifactId>
+ <version>1.4.7</version>
+ </dependency>
+
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
- <version>1.7.30</version> <!-- 版本可以根据需要调整 -->
+ <version>1.7.32</version> <!-- 版本可以根据需要调整 -->
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
@@ -129,11 +145,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- <dependency>
- <groupId>com.baidubce</groupId>
- <artifactId>qianfan</artifactId>
- <version>0.1.1</version>
- </dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
@@ -144,16 +156,68 @@
<version>4.12</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <scope>test</scope>
- </dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-common</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+
+ <!-- ttorrent -->
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-client</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-cli</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-network</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-test-api</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-bencoding</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-tests</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-tracker</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.xiaoymin</groupId>
+ <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
+ <version>4.5.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-spring-boot-starter</artifactId>
+ <version>${sa-token.version}</version>
+ </dependency>
+
+
+
<dependency>
<groupId>jakarta.persistence</groupId>
@@ -208,7 +272,8 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>2.9</version>
<configuration>
- <!--suppress UnresolvedMavenProperty -->
+ <!--suppress
+ UnresolvedMavenProperty -->
<argLine>${surefireArgLine}</argLine>
<skipTests>false</skipTests>
<includes>
@@ -227,14 +292,22 @@
</plugin>
</plugins>
- <resources>
- <resource>
- <directory>src/main/java</directory>
- <includes>
- <include>**/*.xml</include>
- </includes>
- </resource>
- </resources>
+<!-- <resources>-->
+<!-- <resource>-->
+<!-- <directory>src/main/java</directory>-->
+<!-- <includes>-->
+<!-- <include>**/*.xml</include>-->
+<!-- </includes>-->
+<!-- </resource>-->
+<!-- <resource>-->
+<!-- <directory>libs</directory>-->
+<!-- <includes>-->
+<!-- <include>*.jar</include>-->
+<!-- </includes>-->
+<!-- </resource>-->
+<!-- </resources>-->
+
+
</build>
</project>
\ No newline at end of file
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;
diff --git a/target/classes/application.properties b/target/classes/application.properties
index b753da7..0590333 100644
--- a/target/classes/application.properties
+++ b/target/classes/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/target/classes/com/example/myproject/MyProjectApplication.class b/target/classes/com/example/myproject/MyProjectApplication.class
index 1ebd349..a1281a9 100644
--- a/target/classes/com/example/myproject/MyProjectApplication.class
+++ b/target/classes/com/example/myproject/MyProjectApplication.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/CommonResultStatus.class b/target/classes/com/example/myproject/common/CommonResultStatus.class
new file mode 100644
index 0000000..a330e2c
--- /dev/null
+++ b/target/classes/com/example/myproject/common/CommonResultStatus.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/Constants$Announce.class b/target/classes/com/example/myproject/common/Constants$Announce.class
new file mode 100644
index 0000000..3e02afe
--- /dev/null
+++ b/target/classes/com/example/myproject/common/Constants$Announce.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/Constants$FinishStatus.class b/target/classes/com/example/myproject/common/Constants$FinishStatus.class
new file mode 100644
index 0000000..271898f
--- /dev/null
+++ b/target/classes/com/example/myproject/common/Constants$FinishStatus.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/Constants$Order.class b/target/classes/com/example/myproject/common/Constants$Order.class
new file mode 100644
index 0000000..a4b08ca
--- /dev/null
+++ b/target/classes/com/example/myproject/common/Constants$Order.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/Constants$Source.class b/target/classes/com/example/myproject/common/Constants$Source.class
new file mode 100644
index 0000000..afcbfed
--- /dev/null
+++ b/target/classes/com/example/myproject/common/Constants$Source.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/Constants.class b/target/classes/com/example/myproject/common/Constants.class
new file mode 100644
index 0000000..edce466
--- /dev/null
+++ b/target/classes/com/example/myproject/common/Constants.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/ResultStatus.class b/target/classes/com/example/myproject/common/ResultStatus.class
new file mode 100644
index 0000000..d3a6700
--- /dev/null
+++ b/target/classes/com/example/myproject/common/ResultStatus.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/I18nMessage.class b/target/classes/com/example/myproject/common/base/I18nMessage.class
new file mode 100644
index 0000000..c4ae319
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/I18nMessage.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/OrderPageParam.class b/target/classes/com/example/myproject/common/base/OrderPageParam.class
new file mode 100644
index 0000000..032ec40
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/OrderPageParam.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/PageParam.class b/target/classes/com/example/myproject/common/base/PageParam.class
new file mode 100644
index 0000000..2285fc1
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/PageParam.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/PageUtil.class b/target/classes/com/example/myproject/common/base/PageUtil.class
new file mode 100644
index 0000000..818080e
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/PageUtil.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/ResPage.class b/target/classes/com/example/myproject/common/base/ResPage.class
new file mode 100644
index 0000000..e208933
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/ResPage.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/Result.class b/target/classes/com/example/myproject/common/base/Result.class
new file mode 100644
index 0000000..8492dd1
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/Result.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/base/Status.class b/target/classes/com/example/myproject/common/base/Status.class
new file mode 100644
index 0000000..e7f372b
--- /dev/null
+++ b/target/classes/com/example/myproject/common/base/Status.class
Binary files differ
diff --git a/target/classes/com/example/myproject/common/exception/RocketPTException.class b/target/classes/com/example/myproject/common/exception/RocketPTException.class
new file mode 100644
index 0000000..89a4931
--- /dev/null
+++ b/target/classes/com/example/myproject/common/exception/RocketPTException.class
Binary files differ
diff --git a/target/classes/com/example/myproject/config/MyMetaObjectHandler.class b/target/classes/com/example/myproject/config/MyMetaObjectHandler.class
new file mode 100644
index 0000000..c213aff
--- /dev/null
+++ b/target/classes/com/example/myproject/config/MyMetaObjectHandler.class
Binary files differ
diff --git a/target/classes/com/example/myproject/config/TrackerConfig.class b/target/classes/com/example/myproject/config/TrackerConfig.class
new file mode 100644
index 0000000..774aa27
--- /dev/null
+++ b/target/classes/com/example/myproject/config/TrackerConfig.class
Binary files differ
diff --git a/target/classes/com/example/myproject/controller/TorrentController.class b/target/classes/com/example/myproject/controller/TorrentController.class
new file mode 100644
index 0000000..130ba3f
--- /dev/null
+++ b/target/classes/com/example/myproject/controller/TorrentController.class
Binary files differ
diff --git a/target/classes/com/example/myproject/controller/UserController$EmailRequest.class b/target/classes/com/example/myproject/controller/UserController$EmailRequest.class
index d12dede..04ffd85 100644
--- a/target/classes/com/example/myproject/controller/UserController$EmailRequest.class
+++ b/target/classes/com/example/myproject/controller/UserController$EmailRequest.class
Binary files differ
diff --git a/target/classes/com/example/myproject/controller/UserController$VerificationRequest.class b/target/classes/com/example/myproject/controller/UserController$VerificationRequest.class
index aa1371e..272505e 100644
--- a/target/classes/com/example/myproject/controller/UserController$VerificationRequest.class
+++ b/target/classes/com/example/myproject/controller/UserController$VerificationRequest.class
Binary files differ
diff --git a/target/classes/com/example/myproject/controller/UserController.class b/target/classes/com/example/myproject/controller/UserController.class
index d1d685c..cd1ab13 100644
--- a/target/classes/com/example/myproject/controller/UserController.class
+++ b/target/classes/com/example/myproject/controller/UserController.class
Binary files differ
diff --git a/target/classes/com/example/myproject/dto/PromotionCreateDTO.class b/target/classes/com/example/myproject/dto/PromotionCreateDTO.class
new file mode 100644
index 0000000..3087d39
--- /dev/null
+++ b/target/classes/com/example/myproject/dto/PromotionCreateDTO.class
Binary files differ
diff --git a/target/classes/com/example/myproject/dto/TorrentUpdateDTO.class b/target/classes/com/example/myproject/dto/TorrentUpdateDTO.class
new file mode 100644
index 0000000..7285868
--- /dev/null
+++ b/target/classes/com/example/myproject/dto/TorrentUpdateDTO.class
Binary files differ
diff --git a/target/classes/com/example/myproject/dto/param/TorrentParam.class b/target/classes/com/example/myproject/dto/param/TorrentParam.class
new file mode 100644
index 0000000..ad8a2c5
--- /dev/null
+++ b/target/classes/com/example/myproject/dto/param/TorrentParam.class
Binary files differ
diff --git a/target/classes/com/example/myproject/dto/param/TorrentUploadParam.class b/target/classes/com/example/myproject/dto/param/TorrentUploadParam.class
new file mode 100644
index 0000000..49bbc09
--- /dev/null
+++ b/target/classes/com/example/myproject/dto/param/TorrentUploadParam.class
Binary files differ
diff --git a/target/classes/com/example/myproject/dto/vo/TorrentVO.class b/target/classes/com/example/myproject/dto/vo/TorrentVO.class
new file mode 100644
index 0000000..fa2124e
--- /dev/null
+++ b/target/classes/com/example/myproject/dto/vo/TorrentVO.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/EntityBase.class b/target/classes/com/example/myproject/entity/EntityBase.class
new file mode 100644
index 0000000..a16c52a
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/EntityBase.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/FavoriteEntity.class b/target/classes/com/example/myproject/entity/FavoriteEntity.class
new file mode 100644
index 0000000..a0acd8b
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/FavoriteEntity.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/Promotion.class b/target/classes/com/example/myproject/entity/Promotion.class
new file mode 100644
index 0000000..e5df6dd
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/Promotion.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/TorrentEntity.class b/target/classes/com/example/myproject/entity/TorrentEntity.class
new file mode 100644
index 0000000..cdf20da
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/TorrentEntity.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/User.class b/target/classes/com/example/myproject/entity/User.class
index 62eb5a1..3ee8ffe 100644
--- a/target/classes/com/example/myproject/entity/User.class
+++ b/target/classes/com/example/myproject/entity/User.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/FavoriteMapper.class b/target/classes/com/example/myproject/mapper/FavoriteMapper.class
new file mode 100644
index 0000000..dc075c9
--- /dev/null
+++ b/target/classes/com/example/myproject/mapper/FavoriteMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/PromotionMapper.class b/target/classes/com/example/myproject/mapper/PromotionMapper.class
new file mode 100644
index 0000000..074c856
--- /dev/null
+++ b/target/classes/com/example/myproject/mapper/PromotionMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/TorrentMapper.class b/target/classes/com/example/myproject/mapper/TorrentMapper.class
new file mode 100644
index 0000000..5080dfd
--- /dev/null
+++ b/target/classes/com/example/myproject/mapper/TorrentMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/UserMapper.class b/target/classes/com/example/myproject/mapper/UserMapper.class
index 7b80316..10a9ec1 100644
--- a/target/classes/com/example/myproject/mapper/UserMapper.class
+++ b/target/classes/com/example/myproject/mapper/UserMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/service/PromotionService.class b/target/classes/com/example/myproject/service/PromotionService.class
new file mode 100644
index 0000000..6f0963b
--- /dev/null
+++ b/target/classes/com/example/myproject/service/PromotionService.class
Binary files differ
diff --git a/target/classes/com/example/myproject/service/TorrentService.class b/target/classes/com/example/myproject/service/TorrentService.class
new file mode 100644
index 0000000..c0cfeed
--- /dev/null
+++ b/target/classes/com/example/myproject/service/TorrentService.class
Binary files differ
diff --git a/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class b/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class
new file mode 100644
index 0000000..6984839
--- /dev/null
+++ b/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class
Binary files differ
diff --git a/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class b/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class
new file mode 100644
index 0000000..dc9600d
--- /dev/null
+++ b/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class
Binary files differ
diff --git a/target/classes/com/example/myproject/utils/Result.class b/target/classes/com/example/myproject/utils/Result.class
deleted file mode 100644
index 0a8e59e..0000000
--- a/target/classes/com/example/myproject/utils/Result.class
+++ /dev/null
Binary files differ
diff --git a/target/classes/files/files.torrent b/target/classes/files/files.torrent
new file mode 100644
index 0000000..e04974f
--- /dev/null
+++ b/target/classes/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/target/classes/mapper/FavoriteMapper.xml b/target/classes/mapper/FavoriteMapper.xml
new file mode 100644
index 0000000..1048ec2
--- /dev/null
+++ b/target/classes/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/target/classes/mapper/PromotionMapper.xml b/target/classes/mapper/PromotionMapper.xml
new file mode 100644
index 0000000..72ffd95
--- /dev/null
+++ b/target/classes/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/target/classes/mapper/TorrentMapper.xml b/target/classes/mapper/TorrentMapper.xml
new file mode 100644
index 0000000..d5f018e
--- /dev/null
+++ b/target/classes/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/target/classes/mapper/UserMapper.xml b/target/classes/mapper/UserMapper.xml
new file mode 100644
index 0000000..f03ab0a
--- /dev/null
+++ b/target/classes/mapper/UserMapper.xml
@@ -0,0 +1,21 @@
+<?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.UserMapper">
+ <!-- 根据用户名查找用户 -->
+ <select id="selectByUsername" parameterType="string" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE username = #{username}
+ </select>
+ <!-- 根据用户名和密码查找用户 -->
+ <select id="selectByUsernameAndPassword" parameterType="map" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE username = #{username} AND password = #{password}
+ </select>
+ <!-- 根据邮箱查找用户 -->
+ <select id="selectByEmail" parameterType="string" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE email = #{email}
+ </select>
+ <!-- 根据用户名包含查找用户 -->
+ <select id="selectByUsernameContaining" parameterType="string" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE username LIKE CONCAT('%', #{name}, '%')
+ </select>
+</mapper>
diff --git a/target/classes/mapper/VerificationTokenMapper.xml b/target/classes/mapper/VerificationTokenMapper.xml
new file mode 100644
index 0000000..53b19a5
--- /dev/null
+++ b/target/classes/mapper/VerificationTokenMapper.xml
@@ -0,0 +1,10 @@
+<?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.VerificationTokenMapper">
+ <!-- 通过 token 和 email 查询 VerificationToken -->
+ <select id="findByTokenAndEmail" resultType="com.example.myproject.entity.VerificationToken">
+ SELECT * FROM verification_token
+ WHERE token = #{token} AND email = #{email}
+ </select>
+</mapper>
diff --git a/target/classes/output/valid.torrent b/target/classes/output/valid.torrent
new file mode 100644
index 0000000..6a90e52
--- /dev/null
+++ b/target/classes/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/target/echo-backend-1.0-SNAPSHOT.jar b/target/echo-backend-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..2ae979a
--- /dev/null
+++ b/target/echo-backend-1.0-SNAPSHOT.jar
Binary files differ
diff --git a/target/echo-backend-1.0-SNAPSHOT.jar.original b/target/echo-backend-1.0-SNAPSHOT.jar.original
new file mode 100644
index 0000000..4fd8014
--- /dev/null
+++ b/target/echo-backend-1.0-SNAPSHOT.jar.original
Binary files differ
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..07c940e
--- /dev/null
+++ b/target/maven-archiver/pom.properties
@@ -0,0 +1,3 @@
+artifactId=echo-backend
+groupId=groupId
+version=1.0-SNAPSHOT
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..8994d0a
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,58 @@
+com\example\myproject\common\Constants$FinishStatus.class
+com\example\myproject\config\MyMetaObjectHandler.class
+com\example\myproject\service\serviceImpl\UserServiceImpl.class
+com\example\myproject\controller\UserController.class
+com\example\myproject\dto\vo\TorrentVO.class
+com\example\myproject\dto\PromotionCreateDTO.class
+com\example\myproject\controller\TorrentController.class
+com\example\myproject\service\UserService.class
+com\example\myproject\common\Constants$Order.class
+com\example\myproject\entity\UserDetails.class
+com\example\myproject\common\base\Result.class
+com\example\myproject\entity\Promotion.class
+com\example\myproject\common\base\ResPage.class
+com\example\myproject\mapper\TorrentMapper.class
+com\example\myproject\config\SecurityConfig.class
+com\example\myproject\service\InvitationService.class
+com\example\myproject\service\serviceImpl\TorrentServiceImpl.class
+com\example\myproject\mapper\VerificationTokenMapper.class
+com\example\myproject\entity\TorrentEntity.class
+com\example\myproject\entity\EntityBase.class
+com\example\myproject\dto\param\TorrentParam.class
+com\example\myproject\service\serviceImpl\UserDetailsServiceImpl.class
+com\example\myproject\service\UserDetailsService.class
+com\example\myproject\service\TorrentService.class
+com\example\myproject\common\base\I18nMessage.class
+com\example\myproject\common\Constants.class
+com\example\myproject\entity\VerificationToken.class
+com\example\myproject\mapper\InvitationMapper.class
+com\example\myproject\common\base\OrderPageParam.class
+com\example\myproject\service\serviceImpl\InvitationServiceImpl.class
+com\example\myproject\config\GlobalCorsConfig.class
+com\example\myproject\mapper\UserMapper.class
+com\example\myproject\MyProjectApplication.class
+com\example\myproject\config\TrackerConfig.class
+com\example\myproject\mapper\FavoriteMapper.class
+com\example\myproject\common\Constants$Source.class
+com\example\myproject\common\ResultStatus.class
+com\example\myproject\dto\TorrentUpdateDTO.class
+com\example\myproject\common\base\PageUtil.class
+com\example\myproject\config\GlobalCorsConfig$1.class
+com\example\myproject\common\Constants$Announce.class
+com\example\myproject\common\CommonResultStatus.class
+com\example\myproject\controller\UserController$VerificationRequest.class
+com\example\myproject\service\serviceImpl\EmailServiceImpl.class
+com\example\myproject\controller\UserController$EmailRequest.class
+com\example\myproject\service\serviceImpl\PromotionServiceImpl.class
+com\example\myproject\common\base\Status.class
+com\example\myproject\common\base\PageParam.class
+com\example\myproject\mapper\PromotionMapper.class
+com\example\myproject\common\exception\RocketPTException.class
+com\example\myproject\entity\User.class
+com\example\myproject\utils\VerifyCode.class
+com\example\myproject\dto\param\InviteParam.class
+com\example\myproject\dto\param\TorrentUploadParam.class
+com\example\myproject\entity\InvitationEntity.class
+com\example\myproject\service\EmailService.class
+com\example\myproject\service\PromotionService.class
+com\example\myproject\entity\FavoriteEntity.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..15f2807
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,52 @@
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\serviceImpl\InvitationServiceImpl.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\CommonResultStatus.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\config\SecurityConfig.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\dto\TorrentUpdateDTO.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\mapper\UserMapper.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\PageParam.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\EntityBase.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\UserDetailsService.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\exception\RocketPTException.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\mapper\FavoriteMapper.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\dto\param\TorrentParam.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\mapper\PromotionMapper.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\FavoriteEntity.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\TorrentEntity.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\mapper\VerificationTokenMapper.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\controller\UserController.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\dto\param\InviteParam.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\I18nMessage.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\dto\param\TorrentUploadParam.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\OrderPageParam.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\Promotion.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\InvitationEntity.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\InvitationService.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\serviceImpl\TorrentServiceImpl.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\ResultStatus.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\serviceImpl\EmailServiceImpl.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\Constants.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\config\TrackerConfig.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\ResPage.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\PromotionService.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\serviceImpl\UserServiceImpl.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\serviceImpl\UserDetailsServiceImpl.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\EmailService.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\Status.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\utils\VerifyCode.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\MyProjectApplication.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\mapper\TorrentMapper.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\UserDetails.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\UserService.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\User.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\PageUtil.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\common\base\Result.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\dto\vo\TorrentVO.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\mapper\InvitationMapper.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\TorrentService.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\config\MyMetaObjectHandler.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\entity\VerificationToken.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\service\serviceImpl\PromotionServiceImpl.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\controller\InviteController.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\config\GlobalCorsConfig.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\dto\PromotionCreateDTO.java
+D:\study\学习资源\大三下\echo-backend\src\main\java\com\example\myproject\controller\TorrentController.java
diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
new file mode 100644
index 0000000..bac235b
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
@@ -0,0 +1,2 @@
+com\example\myproject\controller\UserControllerTest$1.class
+com\example\myproject\controller\UserControllerTest.class
diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
new file mode 100644
index 0000000..188dce8
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
@@ -0,0 +1,2 @@
+D:\study\学习资源\大三下\echo-backend\src\test\java\com\example\myproject\controller\UserControllerTest.java
+D:\study\学习资源\大三下\echo-backend\src\test\java\com\example\myproject\controller\TorrentControllerTest.java
diff --git a/target/test-classes/classpath.index b/target/test-classes/classpath.index
new file mode 100644
index 0000000..600aff6
--- /dev/null
+++ b/target/test-classes/classpath.index
Binary files differ
diff --git a/target/test-classes/com/example/myproject/controller/UserControllerTest$1.class b/target/test-classes/com/example/myproject/controller/UserControllerTest$1.class
new file mode 100644
index 0000000..ec2954b
--- /dev/null
+++ b/target/test-classes/com/example/myproject/controller/UserControllerTest$1.class
Binary files differ
diff --git a/target/test-classes/com/example/myproject/controller/UserControllerTest.class b/target/test-classes/com/example/myproject/controller/UserControllerTest.class
new file mode 100644
index 0000000..923ca09
--- /dev/null
+++ b/target/test-classes/com/example/myproject/controller/UserControllerTest.class
Binary files differ