Merge branch 'master' of ssh://gerrit.lilingkun.com:29418/echo-backend
Change-Id: I1b49fb8032ecd127e91b5cc5fe4962e8dabfb1fd
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 0709a14..d38745b 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -1,321 +1,283 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="AutoImportSettings">
- <option name="autoReloadType" value="SELECTIVE" />
- </component>
- <component name="ChangeListManager">
- <list default="true" id="f008fe30-0711-42e2-bb33-17dcfdbad387" name="Changes" comment="pom">
- <change afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/Test.java" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/.idea/dataSources.local.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/dataSources.local.xml" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/.idea/dataSources/6d2a7063-ae4c-471f-ae05-e32dede5d0ec.xml" beforeDir="false" />
- <change beforePath="$PROJECT_DIR$/.idea/jarRepositories.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/jarRepositories.xml" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/TorrentController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/TorrentController.java" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/PromotionMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/PromotionMapper.java" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/common/base/PageParam.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/common/base/PageParam.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/common/base/ResPage.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/common/base/ResPage.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/common/base/Result.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/common/base/Result.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/config/TrackerConfig.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/config/TrackerConfig.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/GroupController.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/GroupController.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/TorrentController.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/TorrentController.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/TrackerController.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/controller/TrackerController.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/PromotionCreateDTO.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/PromotionCreateDTO.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/TorrentUpdateDTO.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/TorrentUpdateDTO.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/TrackerProtocol.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/TrackerProtocol.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/param/AnnounceRequest.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/param/AnnounceRequest.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/param/TorrentParam.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/param/TorrentParam.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/param/TorrentUploadParam.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/param/TorrentUploadParam.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/vo/TorrentVO.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/dto/vo/TorrentVO.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/EntityBase.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/EntityBase.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/FavoriteEntity.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/FavoriteEntity.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/Promotion.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/Promotion.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/TorrentEntity.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/TorrentEntity.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/TorrentReport$TorrentReportBuilder.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/TorrentReport$TorrentReportBuilder.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/TorrentReport.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/TorrentReport.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/entity/VerificationToken.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/entity/VerificationToken.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/CommentService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/CommentService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/DynamicService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/DynamicService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/GroupService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/GroupService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/LevelService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/LevelService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/PostService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/PostService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/UserMessageService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/UserMessageService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/UserService.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/UserService.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/EmailServiceImpl.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/EmailServiceImpl.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/utils/Result.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/utils/Result.class" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/utils/VerifyCode.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/utils/VerifyCode.class" afterDir="false" />
- </list>
- <option name="SHOW_DIALOG" value="false" />
- <option name="HIGHLIGHT_CONFLICTS" value="true" />
- <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
- <option name="LAST_RESOLUTION" value="IGNORE" />
- </component>
- <component name="FileTemplateManagerImpl">
- <option name="RECENT_TEMPLATES">
- <list>
- <option value="Class" />
- </list>
- </option>
- </component>
- <component name="Git.Settings">
- <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
- <option name="ROOT_SYNC" value="SYNC" />
- </component>
- <component name="HighlightingSettingsPerFile">
- <setting file="file://$PROJECT_DIR$/src/main/java/com/example/myproject/config/WebConfig.java" root0="FORCE_HIGHLIGHTING" />
- <setting file="file://$PROJECT_DIR$/src/main/java/com/example/myproject/service/PostService.java" root0="FORCE_HIGHLIGHTING" />
- </component>
- <component name="MarkdownSettingsMigration">
- <option name="stateVersion" value="1" />
- </component>
- <component name="ProblemsViewState">
- <option name="selectedTabId" value="CurrentFile" />
- </component>
- <component name="ProjectColorInfo">{
- "associatedIndex": 0
-}</component>
- <component name="ProjectId" id="2vZNfNTEFyHApdxmHZ7Y0rlJjKB" />
- <component name="ProjectLevelVcsManager" settingsEditedManually="true">
- <ConfirmationsSetting value="2" id="Add" />
- </component>
- <component name="ProjectViewState">
- <option name="hideEmptyMiddlePackages" value="true" />
- <option name="showLibraryContents" value="true" />
- </component>
- <component name="PropertiesComponent"><![CDATA[{
- "keyToString": {
- "ASKED_ADD_EXTERNAL_FILES": "true",
- "JUnit.UserControllerTest.executor": "Run",
- "JUnit.UserControllerTest.testUpdateUserAvatar.executor": "Run",
- "JUnit.UserControllerTest.testUploadUserAvatar.executor": "Run",
- "RequestMappingsPanelOrder0": "0",
- "RequestMappingsPanelOrder1": "1",
- "RequestMappingsPanelWidth0": "75",
- "RequestMappingsPanelWidth1": "75",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.git.unshallow": "true",
- "Spring Boot.MyProjectApplication.executor": "Run",
- "WebServerToolWindowFactoryState": "false",
- "git-widget-placeholder": "完成pt与bt交互、补充图片资源",
- "ignore.virus.scanning.warn.message": "true",
- "kotlin-language-version-configured": "true",
- "last_opened_file_path": "D:/EchoTorrent/echo-backend",
- "node.js.detected.package.eslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings",
- "spring.configuration.checksum": "7395fbe190575546ce46bbbb7113b62b",
- "vue.rearranger.settings.migration": "true",
- "应用程序.MyProjectApplication.executor": "Run"
- },
- "keyToStringList": {
- "DatabaseDriversLRU": [
- "mysql_aurora"
- ]
- }
-}]]></component>
- <component name="RecentsManager">
- <key name="CopyFile.RECENT_KEYS">
- <recent name="D:\Desktop\echo后端\echo-backend\images" />
- <recent name="D:\Desktop\echo后端\echo-backend\uploads\dynamic" />
- <recent name="D:\PT\echo-backend\src\main\java\com\example\myproject\config" />
- <recent name="D:\PT\echo-backend\src\main\java\com\example\myproject\service" />
- <recent name="D:\PT\echo-backend\src\main\java\com\example\myproject\controller" />
- </key>
- <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="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="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>
- <configuration name="UserControllerTest" 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.UserControllerTest" />
- <option name="TEST_OBJECT" value="class" />
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
- <configuration name="UserControllerTest.testUpdateUserAvatar" 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.UserControllerTest" />
- <option name="METHOD_NAME" value="testUpdateUserAvatar" />
- <option name="TEST_OBJECT" value="method" />
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
- <configuration name="UserControllerTest.testUploadUserAvatar" 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.UserControllerTest" />
- <option name="METHOD_NAME" value="testUploadUserAvatar" />
- <option name="TEST_OBJECT" value="method" />
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
- <configuration name="MyProjectApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
- <module name="echo-backend" />
- <option name="SPRING_BOOT_MAIN_CLASS" value="com.example.myproject.MyProjectApplication" />
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
- <recent_temporary>
- <list>
- <item itemvalue="应用程序.MyProjectApplication" />
- <item itemvalue="JUnit.UserControllerTest.testUploadUserAvatar" />
- <item itemvalue="JUnit.UserControllerTest.testUpdateUserAvatar" />
- <item itemvalue="JUnit.UserControllerTest" />
- <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">
- <changelist id="f008fe30-0711-42e2-bb33-17dcfdbad387" name="Changes" comment="" />
- <created>1744348740342</created>
- <option name="number" value="Default" />
- <option name="presentableId" value="Default" />
- <updated>1744348740342</updated>
- <workItem from="1749007243227" duration="1245000" />
- <workItem from="1749033374884" duration="7535000" />
- <workItem from="1749048857906" duration="4388000" />
- <workItem from="1749109821491" duration="5517000" />
- <workItem from="1749206945935" duration="4467000" />
- <workItem from="1749228924144" duration="1349000" />
- </task>
- <task id="LOCAL-00001" summary="pom">
- <option name="closed" value="true" />
- <created>1749011916131</created>
- <option name="number" value="00001" />
- <option name="presentableId" value="LOCAL-00001" />
- <option name="project" value="LOCAL" />
- <updated>1749011916131</updated>
- </task>
- <task id="LOCAL-00002" summary="pom">
- <option name="closed" value="true" />
- <created>1749012141761</created>
- <option name="number" value="00002" />
- <option name="presentableId" value="LOCAL-00002" />
- <option name="project" value="LOCAL" />
- <updated>1749012141761</updated>
- </task>
- <option name="localTasksCounter" value="3" />
- <servers />
- </component>
- <component name="TypeScriptGeneratedFilesManager">
- <option name="version" value="3" />
- </component>
- <component name="Vcs.Log.Tabs.Properties">
- <option name="TAB_STATES">
- <map>
- <entry key="MAIN">
- <value>
- <State>
- <option name="FILTERS">
- <map>
- <entry key="branch">
+ <component name="AutoImportSettings">
+ <option name="autoReloadType" value="SELECTIVE" />
+ </component>
+ <component name="ChangeListManager">
+ <list default="true" id="f008fe30-0711-42e2-bb33-17dcfdbad387" name="Changes" comment="pom">
+ <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/PromotionController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/controller/PromotionController.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/PromotionMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/mapper/PromotionMapper.java" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java" afterDir="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$/target/classes/com/example/myproject/mapper/PromotionMapper.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/mapper/PromotionMapper.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class" beforeDir="false" afterPath="$PROJECT_DIR$/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class" afterDir="false" />
+ </list>
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="FileTemplateManagerImpl">
+ <option name="RECENT_TEMPLATES">
+ <list>
+ <option value="Class" />
+ </list>
+ </option>
+ </component>
+ <component name="Git.Settings">
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+ <option name="ROOT_SYNC" value="SYNC" />
+ </component>
+ <component name="HighlightingSettingsPerFile">
+ <setting file="file://$PROJECT_DIR$/src/main/java/com/example/myproject/config/WebConfig.java" root0="FORCE_HIGHLIGHTING" />
+ <setting file="file://$PROJECT_DIR$/src/main/java/com/example/myproject/service/PostService.java" root0="FORCE_HIGHLIGHTING" />
+ </component>
+ <component name="MarkdownSettingsMigration">
+ <option name="stateVersion" value="1" />
+ </component>
+ <component name="ProblemsViewState">
+ <option name="selectedTabId" value="CurrentFile" />
+ </component>
+ <component name="ProjectColorInfo">{
+ "associatedIndex": 0
+ }</component>
+ <component name="ProjectId" id="2vZNfNTEFyHApdxmHZ7Y0rlJjKB" />
+ <component name="ProjectLevelVcsManager" settingsEditedManually="true">
+ <ConfirmationsSetting value="2" id="Add" />
+ </component>
+ <component name="ProjectViewState">
+ <option name="hideEmptyMiddlePackages" value="true" />
+ <option name="showLibraryContents" value="true" />
+ </component>
+ <component name="PropertiesComponent">{
+ "keyToString": {
+ "ASKED_ADD_EXTERNAL_FILES": "true",
+ "JUnit.UserControllerTest.executor": "Run",
+ "JUnit.UserControllerTest.testUpdateUserAvatar.executor": "Run",
+ "JUnit.UserControllerTest.testUploadUserAvatar.executor": "Run",
+ "RequestMappingsPanelOrder0": "0",
+ "RequestMappingsPanelOrder1": "1",
+ "RequestMappingsPanelWidth0": "75",
+ "RequestMappingsPanelWidth1": "75",
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "Spring Boot.MyProjectApplication.executor": "Run",
+ "git-widget-placeholder": "完成pt与bt交互、补充图片资源",
+ "ignore.virus.scanning.warn.message": "true",
+ "kotlin-language-version-configured": "true",
+ "last_opened_file_path": "D:/Desktop/echo后端/echo-backend/images",
+ "node.js.detected.package.eslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings",
+ "vue.rearranger.settings.migration": "true",
+ "应用程序.MyProjectApplication.executor": "Run"
+ },
+ "keyToStringList": {
+ "DatabaseDriversLRU": [
+ "mysql_aurora"
+ ]
+ }
+ }</component>
+ <component name="RecentsManager">
+ <key name="CopyFile.RECENT_KEYS">
+ <recent name="D:\Desktop\echo后端\echo-backend\images" />
+ <recent name="D:\Desktop\echo后端\echo-backend\uploads\dynamic" />
+ <recent name="D:\PT\echo-backend\src\main\java\com\example\myproject\config" />
+ <recent name="D:\PT\echo-backend\src\main\java\com\example\myproject\service" />
+ <recent name="D:\PT\echo-backend\src\main\java\com\example\myproject\controller" />
+ </key>
+ <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="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="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>
+ <configuration name="UserControllerTest" 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.UserControllerTest" />
+ <option name="TEST_OBJECT" value="class" />
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="UserControllerTest.testUpdateUserAvatar" 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.UserControllerTest" />
+ <option name="METHOD_NAME" value="testUpdateUserAvatar" />
+ <option name="TEST_OBJECT" value="method" />
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="UserControllerTest.testUploadUserAvatar" 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.UserControllerTest" />
+ <option name="METHOD_NAME" value="testUploadUserAvatar" />
+ <option name="TEST_OBJECT" value="method" />
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <configuration name="MyProjectApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
+ <module name="echo-backend" />
+ <option name="SPRING_BOOT_MAIN_CLASS" value="com.example.myproject.MyProjectApplication" />
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
+ </configuration>
+ <recent_temporary>
+ <list>
+ <item itemvalue="应用程序.MyProjectApplication" />
+ <item itemvalue="JUnit.UserControllerTest.testUploadUserAvatar" />
+ <item itemvalue="JUnit.UserControllerTest.testUpdateUserAvatar" />
+ <item itemvalue="JUnit.UserControllerTest" />
+ <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">
+ <changelist id="f008fe30-0711-42e2-bb33-17dcfdbad387" name="Changes" comment="" />
+ <created>1744348740342</created>
+ <option name="number" value="Default" />
+ <option name="presentableId" value="Default" />
+ <updated>1744348740342</updated>
+ <workItem from="1749007243227" duration="1245000" />
+ <workItem from="1749033374884" duration="7535000" />
+ <workItem from="1749048857906" duration="4388000" />
+ <workItem from="1749109821491" duration="5517000" />
+ <workItem from="1749206945935" duration="4467000" />
+ </task>
+ <task id="LOCAL-00001" summary="pom">
+ <option name="closed" value="true" />
+ <created>1749011916131</created>
+ <option name="number" value="00001" />
+ <option name="presentableId" value="LOCAL-00001" />
+ <option name="project" value="LOCAL" />
+ <updated>1749011916131</updated>
+ </task>
+ <task id="LOCAL-00002" summary="pom">
+ <option name="closed" value="true" />
+ <created>1749012141761</created>
+ <option name="number" value="00002" />
+ <option name="presentableId" value="LOCAL-00002" />
+ <option name="project" value="LOCAL" />
+ <updated>1749012141761</updated>
+ </task>
+ <option name="localTasksCounter" value="3" />
+ <servers />
+ </component>
+ <component name="TypeScriptGeneratedFilesManager">
+ <option name="version" value="3" />
+ </component>
+ <component name="Vcs.Log.Tabs.Properties">
+ <option name="TAB_STATES">
+ <map>
+ <entry key="MAIN">
<value>
- <list>
- <option value="master" />
- </list>
+ <State>
+ <option name="FILTERS">
+ <map>
+ <entry key="branch">
+ <value>
+ <list>
+ <option value="master" />
+ </list>
+ </value>
+ </entry>
+ </map>
+ </option>
+ </State>
</value>
- </entry>
- </map>
- </option>
- </State>
- </value>
- </entry>
- </map>
- </option>
- </component>
- <component name="VcsManagerConfiguration">
- <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
- <MESSAGE value="pom" />
- <option name="LAST_COMMIT_MESSAGE" value="pom" />
- </component>
- <component name="XDebuggerManager">
- <breakpoint-manager>
- <breakpoints>
- <line-breakpoint enabled="true" type="java-method">
- <url>file://$PROJECT_DIR$/src/test/java/com/example/myproject/controller/UserControllerTest.java</url>
- <line>235</line>
- <properties class="com.example.myproject.controller.UserControllerTest" method="testUploadUserAvatar">
- <option name="EMULATED" value="true" />
- <option name="WATCH_EXIT" value="false" />
- </properties>
- <option name="timeStamp" value="1" />
- </line-breakpoint>
- </breakpoints>
- </breakpoint-manager>
- </component>
+ </entry>
+ </map>
+ </option>
+ </component>
+ <component name="VcsManagerConfiguration">
+ <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
+ <MESSAGE value="pom" />
+ <option name="LAST_COMMIT_MESSAGE" value="pom" />
+ </component>
+ <component name="XDebuggerManager">
+ <breakpoint-manager>
+ <breakpoints>
+ <line-breakpoint enabled="true" type="java-method">
+ <url>file://$PROJECT_DIR$/src/test/java/com/example/myproject/controller/UserControllerTest.java</url>
+ <line>235</line>
+ <properties class="com.example.myproject.controller.UserControllerTest" method="testUploadUserAvatar">
+ <option name="EMULATED" value="true" />
+ <option name="WATCH_EXIT" value="false" />
+ </properties>
+ <option name="timeStamp" value="1" />
+ </line-breakpoint>
+ </breakpoints>
+ </breakpoint-manager>
+ </component>
</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..417fa6d 100644
--- a/src/main/java/com/example/myproject/MyProjectApplication.java
+++ b/src/main/java/com/example/myproject/MyProjectApplication.java
@@ -4,8 +4,11 @@
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
+@EnableScheduling
+
@MapperScan("com.example.myproject.mapper") // 扫描 Mapper 接口
public class MyProjectApplication {
public static void main(String[] args) {
diff --git a/src/main/java/com/example/myproject/controller/PromotionController.java b/src/main/java/com/example/myproject/controller/PromotionController.java
new file mode 100644
index 0000000..ab6efe7
--- /dev/null
+++ b/src/main/java/com/example/myproject/controller/PromotionController.java
@@ -0,0 +1,141 @@
+package com.example.myproject.controller;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.stp.StpUtil;
+import com.example.myproject.common.base.Result;
+import com.example.myproject.dto.PromotionCreateDTO;
+import com.example.myproject.dto.TorrentUpdateDTO;
+import com.example.myproject.entity.Promotion;
+import com.example.myproject.service.PromotionService;
+import com.example.myproject.service.TorrentService;
+import com.example.myproject.service.UserService;
+import com.example.myproject.repository.UserRepository;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+@RestController
+@RequestMapping("/seeds")
+@Slf4j
+@RequiredArgsConstructor
+public class PromotionController {
+ @Autowired
+ private TorrentService torrentService;
+ @Autowired
+ private UserRepository userRepository;
+
+
+ @Autowired
+ private PromotionService promotionService;
+
+ @Autowired
+ private UserService userService;
+ @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("没有权限删除促销活动");
+// }
+ Long userId = StpUtil.getLoginIdAsLong();
+ // 查询用户
+ var userOpt = userRepository.findById(userId);
+ if (userOpt.isEmpty() || !"admin".equals(userOpt.get().getRole())) {
+ return Result.error("没有权限删除促销活动");
+ }
+
+ promotionService.deletePromotion(promotionId);
+ return Result.ok();
+ } catch (Exception e) {
+ return Result.error("删除促销失败: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/example/myproject/controller/TorrentController.java b/src/main/java/com/example/myproject/controller/TorrentController.java
index 6aeb214..ef62c26 100644
--- a/src/main/java/com/example/myproject/controller/TorrentController.java
+++ b/src/main/java/com/example/myproject/controller/TorrentController.java
@@ -5,6 +5,7 @@
import com.example.myproject.dto.TrackerProtocol;
import com.example.myproject.entity.TorrentEntity;
import com.example.myproject.entity.TorrentReport;
+import com.example.myproject.mapper.UserMapper;
import com.example.myproject.repository.TorrentReportRepository;
import com.example.myproject.service.TorrentService;
import com.example.myproject.service.PromotionService;
@@ -15,6 +16,7 @@
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 com.example.myproject.repository.UserRepository;
@@ -62,6 +64,9 @@
@Autowired
private PromotionService promotionService;
+ @Autowired
+ private UserMapper userMapper;
+
@Autowired
private UserService userService;
@@ -215,42 +220,17 @@
return Result.error("失败: ");
}
}
-
- @SaCheckLogin
- @Operation(summary = "删除种子")
- @DeleteMapping("/{torrentId}")
- public Result deleteTorrent(@PathVariable Long torrentId) {
+ /**
+ * 查看个人收藏
+ */
+ @GetMapping("/my-favorite")
+ public Result getMyFavorite(
+ @RequestParam("user_id") Long userId) {
try {
- // 验证用户权限
- Long userId = StpUtil.getLoginIdAsLong();
- if (!torrentService.canUserDeleteTorrent(torrentId, userId)) {
- return Result.error("没有权限删除此种子");
- }
- torrentService.deleteTorrent(torrentId);
- return Result.ok();
+ return torrentService.getMyfavorite(userId);
} 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());
+ return Result.error("失败: ");
}
}
@@ -330,13 +310,25 @@
trackerProtocol.setInfo_hash(infoHash);
TorrentEntity torrent = torrentService.selectByInfoHash(infoHash);
+ Long torrentId = torrent.getId();
+ String peerId = trackerProtocol.getPeer_id();
+
if (torrent == null) {
throw new RuntimeException("种子不存在");
}
Integer userId = trackerProtocol.getUserId();
+ double uploaded = trackerProtocol.getUploaded();
+ double downloaded = trackerProtocol.getDownloaded();
+ /**
+ *更新种子上传量,下载量, 更新用户上传量下载量
+ * 为了总量检测作弊,种子的上传量和下载量是真实的
+ * 用户的上传下载量是“促销”后的
+ * 作弊检测——下载速度检测
+ */
+ torrentService.processUploadDownload(Long.valueOf(userId),peerId, infoHash,torrentId,uploaded,downloaded);
TorrentReport report = TorrentReport.builder()
- .userId(userId )
+ .userId(userId)
.torrentId(torrent.getId())
.peerId(trackerProtocol.getPeer_id())
.infoHash(infoHash)
diff --git a/src/main/java/com/example/myproject/entity/AuditRecord.java b/src/main/java/com/example/myproject/entity/AuditRecord.java
new file mode 100644
index 0000000..ed8b9de
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/AuditRecord.java
@@ -0,0 +1,20 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("audit_record")
+@ApiModel("种子实体类")
+public class AuditRecord {
+ private Integer id;
+ private Long torrentId;
+ private double upload;
+ private double download;
+ private LocalDateTime createTime;
+ public AuditRecord() {
+ }
+}
diff --git a/src/main/java/com/example/myproject/entity/BannedUser.java b/src/main/java/com/example/myproject/entity/BannedUser.java
new file mode 100644
index 0000000..a9aa2da
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/BannedUser.java
@@ -0,0 +1,27 @@
+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.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+@Data
+@TableName("ba")
+public class BannedUser {
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty(value = "用户ID", example = "1001")
+ @JsonProperty("userId")
+ @TableField("user_id")
+ private Long userId;
+
+ private String reason;
+ @JsonProperty("createTime")
+ @TableField("create_time")
+ private Date createTime;
+}
diff --git a/src/main/java/com/example/myproject/entity/SuspiciousUser.java b/src/main/java/com/example/myproject/entity/SuspiciousUser.java
new file mode 100644
index 0000000..8b15f08
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/SuspiciousUser.java
@@ -0,0 +1,35 @@
+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("suspicious_user")
+@ApiModel("列入怀疑用户")
+public class SuspiciousUser {
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty(value = "用户ID", example = "1001")
+ @JsonProperty("userId")
+ @TableField("user_id")
+ private Long userId;
+ @ApiModelProperty(value = "上传速度", example = "1001")
+ @TableField("peed_kbs")
+ private double speedKBs;
+
+ private String reason;
+ @JsonProperty("createTime")
+ @TableField("create_time")
+ private Date createTime;
+}
\ 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
index f62fb76..b2b8192 100644
--- a/src/main/java/com/example/myproject/entity/TorrentEntity.java
+++ b/src/main/java/com/example/myproject/entity/TorrentEntity.java
@@ -93,6 +93,12 @@
@JsonProperty("promotionUntil")
@ApiModelProperty(value = "促销截止日期", example = "2024-12-31T23:59:59")
private LocalDateTime promotionUntil;
+ @JsonProperty("upload_count")
+ @ApiModelProperty(value = "种子上传量", example = "false")
+ private double uploadCount;
+ @JsonProperty("down_count")
+ @ApiModelProperty(value = "种子下载量", example = "false")
+ private double downloadCount;
@JsonProperty("torrentFile")
@@ -103,6 +109,7 @@
@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/TorrentReport.java b/src/main/java/com/example/myproject/entity/TorrentReport.java
index a019f9e..0d6de00 100644
--- a/src/main/java/com/example/myproject/entity/TorrentReport.java
+++ b/src/main/java/com/example/myproject/entity/TorrentReport.java
@@ -1,5 +1,7 @@
package com.example.myproject.entity;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Builder;
import lombok.Data;
@@ -24,5 +26,12 @@
private String event;
private String ip;
private Integer port;
+
private LocalDateTime reportTime;
+ @PrePersist
+ public void prePersist() {
+ this.reportTime = LocalDateTime.now();
+ }
+
+
}
diff --git a/src/main/java/com/example/myproject/mapper/AuditRecordMapper.java b/src/main/java/com/example/myproject/mapper/AuditRecordMapper.java
new file mode 100644
index 0000000..2aae334
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/AuditRecordMapper.java
@@ -0,0 +1,13 @@
+package com.example.myproject.mapper;
+
+import com.example.myproject.entity.AuditRecord;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AuditRecordMapper {
+
+ @Insert("INSERT INTO audit_record (torrent_id, upload, download, create_time) " +
+ "VALUES (#{torrentId}, #{upload}, #{download}, #{createTime})")
+ void insert(AuditRecord record);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/mapper/BannedUserMapper.java b/src/main/java/com/example/myproject/mapper/BannedUserMapper.java
new file mode 100644
index 0000000..9b376e6
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/BannedUserMapper.java
@@ -0,0 +1,15 @@
+package com.example.myproject.mapper;
+
+import com.example.myproject.entity.AuditRecord;
+import com.example.myproject.entity.BannedUser;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+
+
+@Mapper
+public interface BannedUserMapper {
+
+ @Insert("INSERT INTO banned_user (user_id, reason, create_time) " +
+ "VALUES (#{userId}, #{reason}, #{createTime})")
+ void insert(BannedUser bannedUser);
+}
\ 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
index b6079b3..4ff79ea 100644
--- a/src/main/java/com/example/myproject/mapper/PromotionMapper.java
+++ b/src/main/java/com/example/myproject/mapper/PromotionMapper.java
@@ -24,7 +24,8 @@
@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")
+// "AND FIND_IN_SET(#{torrentId}, p.applicable_torrent_ids) > 0"
+ "AND JSON_CONTAINS(p.applicable_torrent_ids, CAST(#{torrentId} AS JSON))")
List<Promotion> findActivePromotionsForTorrent(
@Param("torrentId") Long torrentId,
@Param("now") LocalDateTime now);
@@ -44,11 +45,6 @@
* 根据ID更新促销活动(例如软删除)
*/
int updateById(Promotion promotion);
- /**
- * 根据ID更新促销活动(例如软删除)
- */
- @Update("update promotion set is_deleted = 1 WHERE id = #{id}")
- int updateStatusById(@Param("id") Long id);
/**
* 根据ID查询促销活动
diff --git a/src/main/java/com/example/myproject/mapper/SuspiciousUserMapper.java b/src/main/java/com/example/myproject/mapper/SuspiciousUserMapper.java
new file mode 100644
index 0000000..533c597
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/SuspiciousUserMapper.java
@@ -0,0 +1,12 @@
+package com.example.myproject.mapper;
+
+import com.example.myproject.entity.SuspiciousUser;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SuspiciousUserMapper {
+ @Insert("INSERT INTO suspicious_user (user_id, reason, speed_kbs, create_time) " +
+ "VALUES (#{userId}, #{reason}, #{speedKBs}, #{createTime})")
+ void insert(SuspiciousUser user);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/mapper/TorrentMapper.java b/src/main/java/com/example/myproject/mapper/TorrentMapper.java
index f9cdcdc..5e72bf2 100644
--- a/src/main/java/com/example/myproject/mapper/TorrentMapper.java
+++ b/src/main/java/com/example/myproject/mapper/TorrentMapper.java
@@ -15,18 +15,20 @@
@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);
+ List<TorrentEntity> selectMyFavorite(@Param("userId") Long userId);
+ @Select("SELECT * FROM torrent")
+ List<TorrentEntity> findAllTorrents();
+ @Update("UPDATE torrent SET upload_count = upload_count + #{uploaded}, download_count = download_count + #{downloaded} WHERE id = #{torrentId}")
+ void updateTorrentUploadDownload(@Param("torrentId") Long torrentId,
+ @Param("uploaded") double uploaded,
+ @Param("downloaded") double downloaded);
+
}
\ 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 1752801..4a10a38 100644
--- a/src/main/java/com/example/myproject/mapper/UserMapper.java
+++ b/src/main/java/com/example/myproject/mapper/UserMapper.java
@@ -1,4 +1,5 @@
package com.example.myproject.mapper;
+import com.example.myproject.entity.TorrentEntity;
import com.example.myproject.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -30,6 +31,14 @@
@Select("SELECT * FROM user WHERE user_id = #{userId}")
User selectByUserId(@Param("userId") Long userId);
+ @Update("UPDATE user SET upload_count = upload_count + #{bonusUploaded}, download_count = download_count + #{bonusDownloaded} WHERE user_id = #{userId}")
+ int updateUserUploadDownload(@Param("userId") Long userId,
+ @Param("bonusUploaded") double bonusUploaded,
+ @Param("bonusDownloaded") double bonusDownloaded);
+
+
+
+
}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/repository/TorrentReportRepository.java b/src/main/java/com/example/myproject/repository/TorrentReportRepository.java
index 9484d20..7b3e89c 100644
--- a/src/main/java/com/example/myproject/repository/TorrentReportRepository.java
+++ b/src/main/java/com/example/myproject/repository/TorrentReportRepository.java
@@ -2,6 +2,12 @@
import com.example.myproject.entity.TorrentReport;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.Optional;
public interface TorrentReportRepository extends JpaRepository<TorrentReport, Integer> {
+ @Query(value = "SELECT * FROM TorrentReport WHERE peerId = :peerId AND infoHash = :infoHash ORDER BY id DESC LIMIT 1", nativeQuery = true)
+ Optional<TorrentReport> findLatestByPeerIdAndInfoHash(@Param("peerId") String peerId, @Param("infoHash") String infoHash);
}
diff --git a/src/main/java/com/example/myproject/scheduler/TorrentAuditTask.java b/src/main/java/com/example/myproject/scheduler/TorrentAuditTask.java
new file mode 100644
index 0000000..bd985ad
--- /dev/null
+++ b/src/main/java/com/example/myproject/scheduler/TorrentAuditTask.java
@@ -0,0 +1,49 @@
+package com.example.myproject.scheduler;
+
+
+
+import com.example.myproject.entity.TorrentEntity;
+import com.example.myproject.mapper.TorrentMapper;
+import com.example.myproject.service.AuditService;
+import com.example.myproject.service.TorrentService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 作弊检测——总量核验
+ */
+@Slf4j
+@Component
+public class TorrentAuditTask {
+
+ private static final long THRESHOLD = 50 * 1024 * 1024; // 50MB
+
+ private final TorrentMapper torrentMapper;
+ private final AuditService auditService;
+
+ public TorrentAuditTask(TorrentMapper torrentMapper, AuditService auditService) {
+ this.torrentMapper = torrentMapper;
+ this.auditService = auditService;
+ }
+
+ @Scheduled(fixedRate = 3600000) // 每小时执行一次
+ public void checkTorrentUploadDownloadConsistency() {
+ List<TorrentEntity> torrents = torrentMapper.findAllTorrents();
+ for (TorrentEntity torrent : torrents) {
+ double upload = torrent.getUploadCount();
+ double download = torrent.getDownloadCount();
+ double delta = Math.abs(upload - download);
+
+ if (delta > THRESHOLD) {
+ log.warn("【总量核验】种子 {} 上传量={} 下载量={} 差值={},已触发人工审核",
+ torrent.getInfoHash(), upload, download, delta);
+
+ auditService.flagTorrentForReview(torrent.getId(), upload, download);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/AuditService.java b/src/main/java/com/example/myproject/service/AuditService.java
new file mode 100644
index 0000000..22d7124
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/AuditService.java
@@ -0,0 +1,46 @@
+package com.example.myproject.service;
+
+import com.example.myproject.entity.AuditRecord;
+import com.example.myproject.entity.BannedUser;
+import com.example.myproject.entity.SuspiciousUser;
+import com.example.myproject.mapper.AuditRecordMapper;
+import com.example.myproject.mapper.BannedUserMapper;
+import com.example.myproject.mapper.SuspiciousUserMapper;
+import org.checkerframework.checker.units.qual.A;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+@Service
+public class AuditService {
+ @Autowired
+ AuditRecordMapper auditRecordMapper;
+ @Autowired
+ SuspiciousUserMapper suspiciousUserMapper;
+ @Autowired
+ BannedUserMapper bannedUserMapper;
+ public void flagTorrentForReview(Long torrentId, double upload, double download) {
+ AuditRecord record = new AuditRecord();
+ record.setTorrentId(torrentId);
+ record.setUpload(upload);
+ record.setDownload(download);
+ record.setCreateTime(LocalDateTime.now());
+
+ auditRecordMapper.insert(record);
+ }
+ public void addSuspiciousUser(Long userId, String reason, double speedKBs) {
+ SuspiciousUser user = new SuspiciousUser();
+ user.setUserId(userId);
+ user.setReason(reason);
+ user.setSpeedKBs(speedKBs);
+ suspiciousUserMapper.insert(user);
+ }
+
+
+ public void banUser(Long userId, String reason) {
+ BannedUser bannedUser = new BannedUser();
+ bannedUser.setUserId(userId);
+ bannedUser.setReason(reason);
+ bannedUserMapper.insert(bannedUser);
+ }
+}
diff --git a/src/main/java/com/example/myproject/service/PromotionService.java b/src/main/java/com/example/myproject/service/PromotionService.java
index 087c13b..b83bce5 100644
--- a/src/main/java/com/example/myproject/service/PromotionService.java
+++ b/src/main/java/com/example/myproject/service/PromotionService.java
@@ -13,5 +13,9 @@
void deletePromotion(Long promotionId);
- double getCurrentDiscount(Long torrentId);
+// double getCurrentDiscount(Long torrentId);
+ double getUploadBonus(Long torrentId);
+ double getDownloadDiscount(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
index 36891d2..e0a4f41 100644
--- a/src/main/java/com/example/myproject/service/TorrentService.java
+++ b/src/main/java/com/example/myproject/service/TorrentService.java
@@ -57,7 +57,9 @@
byte[] fetch(Long seedId, String passkey) throws IOException;
Result favorite(Long seedId, Long userId);
-
+ Result getMyfavorite(Long userId);
+
+
void deleteTorrent(Long seedId);
void updateTorrent(Long seedId, TorrentUpdateDTO updateDTO);
@@ -68,9 +70,12 @@
boolean checkUserUploadRatio(Long userId);
- double calculateDownloadSize(Long torrentId, Long userId);
+// double calculateDownloadSize(Long torrentId, Long userId);
- void recordDownload(Long torrentId, Long userId, double downloadSize);
+
TorrentEntity selectByInfoHash(String infoHash);
+
+ void processUploadDownload(Long userId, String peerId, String infoHash, Long torrentId, double uploaded, double downloaded);
+
}
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
index 5da10e5..1cac3a4 100644
--- a/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
+++ b/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
@@ -1,12 +1,14 @@
package com.example.myproject.service.serviceImpl;
import com.example.myproject.entity.Promotion;
+import com.example.myproject.entity.TorrentEntity;
import com.example.myproject.mapper.PromotionMapper;
import com.example.myproject.service.PromotionService;
import com.example.myproject.dto.PromotionCreateDTO;
+
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-
+import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -41,6 +43,11 @@
promotion.setEndTime(promotionDTO.getEndTime());
promotion.setDiscountPercentage(promotionDTO.getDiscountPercentage());
+ // 把List<Long>转换成逗号分隔字符串
+// String applicableTorrentIdsStr = promotionDTO.getApplicableTorrentIds().stream()
+// .map(String::valueOf)
+// .collect(Collectors.joining(","));
+// promotion.setApplicableTorrentIds(applicableTorrentIdsStr);
// 把List<Long>转换成JSON字符串
ObjectMapper objectMapper = new ObjectMapper();
try {
@@ -52,7 +59,7 @@
promotion.setCreateTime(now);
promotion.setUpdateTime(now);
- promotion.setIsDeleted(true);
+ promotion.setIsDeleted(false);
promotionMapper.insert(promotion);
return promotion;
@@ -82,21 +89,22 @@
}
// 软删除
+ promotion.setIsDeleted(true);
promotion.setUpdateTime(LocalDateTime.now());
- promotionMapper.updateStatusById(promotion.getId());
+ 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);
- }
+// @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是否存在
@@ -115,4 +123,48 @@
throw new RuntimeException("以下种子ID不存在: " + invalidIds);
}
}
+ @Override
+ public double getUploadBonus(Long torrentId) {
+ double uploadBonusPercentage = getUploadBonusPercentage(torrentId); // 例如 26.4
+ return 1.0 + (uploadBonusPercentage / 100.0); // 转换为倍数,例如 1.264
+ }
+
+ @Override
+ public double getDownloadDiscount(Long torrentId) {
+ double downloadDiscountPercentage = getDownloadDiscountPercentage(torrentId); // 例如 -20.0
+ return 1.0 + (downloadDiscountPercentage / 100.0); // -20.0 → 0.8
+ }
+
+
+ /**
+ * 获取当前种子适用的上传加成百分比(正数),若无返回 0.0
+ */
+ private double getUploadBonusPercentage(Long torrentId) {
+ LocalDateTime now = LocalDateTime.now();
+ List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
+
+ // 取最大的正值加成百分比
+ return activePromotions.stream()
+ .mapToDouble(Promotion::getDiscountPercentage)
+ .filter(p -> p > 0)
+ .max()
+ .orElse(0.0);
+ }
+
+ /**
+ * 获取当前种子适用的下载折扣百分比(负数),若无返回 0.0
+ */
+ private double getDownloadDiscountPercentage(Long torrentId) {
+ LocalDateTime now = LocalDateTime.now();
+ List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
+
+ // 取最小的负值折扣百分比(更大的折扣)
+ return activePromotions.stream()
+ .mapToDouble(Promotion::getDiscountPercentage)
+ .filter(p -> p < 0)
+ .min()
+ .orElse(0.0);
+ }
+
+
}
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
index 4870fd5..210db93 100644
--- a/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
+++ b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
@@ -296,14 +296,19 @@
// }
package com.example.myproject.service.serviceImpl;
+import java.time.Duration;
import cn.dev33.satoken.stp.StpUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.example.myproject.entity.SuspiciousUser;
import com.example.myproject.entity.TorrentEntity;
+import com.example.myproject.entity.TorrentReport;
import com.example.myproject.entity.User;
+import com.example.myproject.mapper.BannedUserMapper;
+import com.example.myproject.mapper.SuspiciousUserMapper;
import com.example.myproject.mapper.TorrentMapper;
import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.repository.TorrentReportRepository;
+import com.example.myproject.service.AuditService;
import com.example.myproject.service.TorrentService;
import com.example.myproject.service.PromotionService;
import com.example.myproject.dto.param.TorrentParam;
@@ -315,13 +320,14 @@
import com.turn.ttorrent.client.SimpleClient;
import com.turn.ttorrent.common.TorrentMetadata;
import com.turn.ttorrent.common.TorrentParser;
-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 lombok.extern.slf4j.Slf4j;
-import com.turn.ttorrent.tracker.TrackedTorrent;
+
import org.apache.commons.codec.binary.Hex;
+import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -331,19 +337,18 @@
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.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+@Slf4j
@Service
public class TorrentServiceImpl implements TorrentService {
@Autowired
@@ -352,6 +357,9 @@
@Autowired
private TorrentMapper torrentMapper;
+ @Autowired
+ private TorrentReportRepository torrentReportRepository;
+
private final Map<String, TrackedTorrent> torrentRegistry = new HashMap<>();
@@ -360,6 +368,12 @@
@Autowired
private PromotionService promotionService;
+ @Autowired
+ SuspiciousUserMapper suspiciousUserMapper;
+ @Autowired
+ BannedUserMapper bannedUserMapper;
+ @Autowired
+ AuditService auditService;
private static final double MIN_UPLOAD_RATIO = 0.5; // 最小上传比例要求
@@ -372,6 +386,7 @@
public TorrentEntity selectBySeedId(Long seedId) {
return torrentMapper.selectById(seedId);
}
+
private final ExecutorService seederExecutor = Executors.newCachedThreadPool();
@Override
@@ -389,7 +404,7 @@
Long size = metadata.getFiles().stream().map(f -> f.size).reduce(0L, Long::sum);
// 保存种子信息
- TorrentEntity entity= new TorrentEntity();
+ TorrentEntity entity = new TorrentEntity();
entity.setUploader(param.getUploader());
entity.setFileName(file.getOriginalFilename());
entity.setSize(size);
@@ -404,7 +419,6 @@
}
-
@Override
public byte[] fetch(Long seedId, String passkey) {
TorrentEntity torrent = selectBySeedId(seedId);
@@ -422,6 +436,7 @@
// TODO: 对应本机应用地址
// String announce = "http://127.0.0.1:5011/seeds/announce?passkey="+passkey +"&userId=" + StpUtil.getLoginIdAsString();
String announce = "http://192.168.5.149:5011/seeds/announce?passkey=" + passkey + "&userId=" + StpUtil.getLoginIdAsString();
+// String announce = "http://172.23.80.1:5011/seeds/announce?passkey=" + passkey + "&userId=" + 1;
decoded.put("announce", new BEValue(announce));
// 4. 编码成新的 .torrent 文件字节数组
@@ -452,6 +467,12 @@
}
@Override
+ public Result getMyfavorite(Long userId) {
+ List<TorrentEntity> favorites = torrentMapper.selectMyFavorite(userId);
+ return Result.ok(favorites);
+ }
+
+ @Override
@Transactional
public void deleteTorrent(Long seedId) {
torrentMapper.deleteById(seedId);
@@ -507,58 +528,78 @@
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);
- }
-
- @Override
public TorrentEntity selectByInfoHash(String infoHash) {
return torrentMapper.selectByInfoHash(infoHash);
}
- /**
- * 计算种子文件的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);
+ @Override
+ public void processUploadDownload(Long userId, String peerId, String infoHash, Long torrentId, double uploaded, double downloaded) {
+ // 查找该peerId和infoHash的最近一条TorrentReport
+ log.info("开始处理上传和下载");
+ Optional<TorrentReport> lastReportOpt = torrentReportRepository.findLatestByPeerIdAndInfoHash(peerId, infoHash);
+
+
+ double lastUploaded = 0;
+ double lastDownloaded = 0;
+ LocalDateTime lastTime = null;
+
+ if (lastReportOpt.isPresent()) {
+ lastUploaded = lastReportOpt.get().getUploaded();
+ lastDownloaded = lastReportOpt.get().getDownloaded();
+ lastTime = lastReportOpt.get().getReportTime();
+ }
+
+ double uploadedOffset = uploaded - lastUploaded;
+ double downloadedOffset = downloaded - lastDownloaded;
+
+ // 防止客户端重启或作弊导致负数,重置为0
+ if (uploadedOffset < 0) uploadedOffset = 0;
+ if (downloadedOffset < 0) downloadedOffset = 0;
+
+ /**
+ * 作弊——速度检测
+ */
+ if (lastTime != null) {
+ long seconds = Duration.between(lastTime, LocalDateTime.now()).getSeconds();
+ if (seconds > 0) {
+ double speedKBs = uploadedOffset / 1024.0 / seconds; // KB/s
+
+ double MAX_SPEED_KB = 10 * 1024; // 10 MB/s
+ double SUSPICIOUS_SPEED_KB = 2 * 1024; // 2 MB/s
+ int MIN_EXPECTED_LEECHERS = 5;
+
+ //int leechers = torrentReportRepository.countActiveLeechers(infoHash);
+
+ if (speedKBs > MAX_SPEED_KB) {
+ log.warn("用户 {} 上传速度 {} KB/s 超过限制,执行封禁处理", userId, speedKBs);
+ auditService.banUser(userId, "上传速度超过最大限速");
+ return; // 不再处理其他操作
+ }
+// else if (speedKBs > SUSPICIOUS_SPEED_KB && leechers < MIN_EXPECTED_LEECHERS) {
+// log.warn("用户 {} 上传速度异常 {} KB/s,下载人数 {},加入怀疑名单", userId, speedKBs, leechers);
+// abnormalUserMapper.insertSuspicious(userId, speedKBs, leechers); // 你也需要实现
+// }
+ else if (speedKBs > SUSPICIOUS_SPEED_KB) {
+ log.warn("用户 {} 上传速度异常 {} KB/s, 加入怀疑名单", userId, speedKBs);
+ auditService.addSuspiciousUser(userId, "上传速度异常:" ,speedKBs );
+ }
+ }
+
+ // 获取促销加成和下载折扣
+ double uploadBonus = promotionService.getUploadBonus(torrentId); // 比如 1.2
+ log.info("上传优惠比例" + uploadBonus);
+ double downloadDiscount = promotionService.getDownloadDiscount(torrentId); // 比如 0.8
+
+ double bonusUploaded = uploadedOffset * uploadBonus;
+ double bonusDownloaded = downloadedOffset * downloadDiscount;
+
+ // 调用Mapper更新数据库
+ userMapper.updateUserUploadDownload(userId, bonusUploaded, bonusDownloaded);
+ torrentMapper.updateTorrentUploadDownload(torrentId, uploadedOffset, downloadedOffset);
+
+ }
}
}
\ No newline at end of file
diff --git a/src/main/resources/mapper/AuditRecordMapper.xml b/src/main/resources/mapper/AuditRecordMapper.xml
new file mode 100644
index 0000000..aa6b018
--- /dev/null
+++ b/src/main/resources/mapper/AuditRecordMapper.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.AuditRecordMapper">
+
+</mapper>
diff --git a/src/main/resources/mapper/BannedUserMapper.xml b/src/main/resources/mapper/BannedUserMapper.xml
new file mode 100644
index 0000000..fbdb038
--- /dev/null
+++ b/src/main/resources/mapper/BannedUserMapper.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.BannedUserMapper">
+
+</mapper>
diff --git a/src/main/resources/mapper/SuspiciousUserMapper.xml b/src/main/resources/mapper/SuspiciousUserMapper.xml
new file mode 100644
index 0000000..27851b2
--- /dev/null
+++ b/src/main/resources/mapper/SuspiciousUserMapper.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.SuspiciousUserMapper">
+
+</mapper>
diff --git a/src/main/resources/mapper/TorrentMapper.xml b/src/main/resources/mapper/TorrentMapper.xml
index d5f018e..8e5419b 100644
--- a/src/main/resources/mapper/TorrentMapper.xml
+++ b/src/main/resources/mapper/TorrentMapper.xml
@@ -99,6 +99,11 @@
DELETE FROM favorite
WHERE seed_id = #{seedId} AND user_id = #{userId}
</delete>
-
+ <select id="selectMyFavorite" resultType="com.example.myproject.entity.TorrentEntity">
+ SELECT t.*
+ FROM torrent t
+ JOIN favorite f ON t.id = f.seed_id
+ WHERE f.user_id = #{userId}
+ </select>
</mapper>
\ No newline at end of file
diff --git a/target/classes/com/example/myproject/MyProjectApplication.class b/target/classes/com/example/myproject/MyProjectApplication.class
index 1ebd349..93fb1c1 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/controller/PromotionController.class b/target/classes/com/example/myproject/controller/PromotionController.class
new file mode 100644
index 0000000..75a63cc
--- /dev/null
+++ b/target/classes/com/example/myproject/controller/PromotionController.class
Binary files differ
diff --git a/target/classes/com/example/myproject/controller/TorrentController.class b/target/classes/com/example/myproject/controller/TorrentController.class
index 4d2ba13..654356b 100644
--- a/target/classes/com/example/myproject/controller/TorrentController.class
+++ b/target/classes/com/example/myproject/controller/TorrentController.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/AuditRecord.class b/target/classes/com/example/myproject/entity/AuditRecord.class
new file mode 100644
index 0000000..3699c9d
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/AuditRecord.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/BannedUser.class b/target/classes/com/example/myproject/entity/BannedUser.class
new file mode 100644
index 0000000..8ca9203
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/BannedUser.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/SuspiciousUser.class b/target/classes/com/example/myproject/entity/SuspiciousUser.class
new file mode 100644
index 0000000..3407b75
--- /dev/null
+++ b/target/classes/com/example/myproject/entity/SuspiciousUser.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/TorrentEntity.class b/target/classes/com/example/myproject/entity/TorrentEntity.class
index cdf20da..44b924b 100644
--- a/target/classes/com/example/myproject/entity/TorrentEntity.class
+++ b/target/classes/com/example/myproject/entity/TorrentEntity.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/TorrentReport$TorrentReportBuilder.class b/target/classes/com/example/myproject/entity/TorrentReport$TorrentReportBuilder.class
index f2dfc4e..17b87a3 100644
--- a/target/classes/com/example/myproject/entity/TorrentReport$TorrentReportBuilder.class
+++ b/target/classes/com/example/myproject/entity/TorrentReport$TorrentReportBuilder.class
Binary files differ
diff --git a/target/classes/com/example/myproject/entity/TorrentReport.class b/target/classes/com/example/myproject/entity/TorrentReport.class
index d64129c..260b34e 100644
--- a/target/classes/com/example/myproject/entity/TorrentReport.class
+++ b/target/classes/com/example/myproject/entity/TorrentReport.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/AuditRecordMapper.class b/target/classes/com/example/myproject/mapper/AuditRecordMapper.class
new file mode 100644
index 0000000..231ffe6
--- /dev/null
+++ b/target/classes/com/example/myproject/mapper/AuditRecordMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/BannedUserMapper.class b/target/classes/com/example/myproject/mapper/BannedUserMapper.class
new file mode 100644
index 0000000..da69e74
--- /dev/null
+++ b/target/classes/com/example/myproject/mapper/BannedUserMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/PromotionMapper.class b/target/classes/com/example/myproject/mapper/PromotionMapper.class
index 08c11f5..909cbb6 100644
--- a/target/classes/com/example/myproject/mapper/PromotionMapper.class
+++ b/target/classes/com/example/myproject/mapper/PromotionMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/SuspiciousUserMapper.class b/target/classes/com/example/myproject/mapper/SuspiciousUserMapper.class
new file mode 100644
index 0000000..ddc85da
--- /dev/null
+++ b/target/classes/com/example/myproject/mapper/SuspiciousUserMapper.class
Binary files differ
diff --git a/target/classes/com/example/myproject/mapper/TorrentMapper.class b/target/classes/com/example/myproject/mapper/TorrentMapper.class
index 5080dfd..56fb640 100644
--- a/target/classes/com/example/myproject/mapper/TorrentMapper.class
+++ 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 74cbed4..d9c95c8 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/repository/TorrentReportRepository.class b/target/classes/com/example/myproject/repository/TorrentReportRepository.class
index 77fc905..8abadf5 100644
--- a/target/classes/com/example/myproject/repository/TorrentReportRepository.class
+++ b/target/classes/com/example/myproject/repository/TorrentReportRepository.class
Binary files differ
diff --git a/target/classes/com/example/myproject/scheduler/TorrentAuditTask.class b/target/classes/com/example/myproject/scheduler/TorrentAuditTask.class
new file mode 100644
index 0000000..88e2a9d
--- /dev/null
+++ b/target/classes/com/example/myproject/scheduler/TorrentAuditTask.class
Binary files differ
diff --git a/target/classes/com/example/myproject/service/AuditService.class b/target/classes/com/example/myproject/service/AuditService.class
new file mode 100644
index 0000000..29cf4b1
--- /dev/null
+++ b/target/classes/com/example/myproject/service/AuditService.class
Binary files differ
diff --git a/target/classes/com/example/myproject/service/PromotionService.class b/target/classes/com/example/myproject/service/PromotionService.class
index 6f0963b..f5d48f0 100644
--- a/target/classes/com/example/myproject/service/PromotionService.class
+++ 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
index bac5d31..9fdf389 100644
--- a/target/classes/com/example/myproject/service/TorrentService.class
+++ 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
index 0a920c0..83cf11d 100644
--- a/target/classes/com/example/myproject/service/serviceImpl/PromotionServiceImpl.class
+++ 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
index a36065f..63669c9 100644
--- a/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class
+++ b/target/classes/com/example/myproject/service/serviceImpl/TorrentServiceImpl.class
Binary files differ
diff --git a/target/classes/mapper/AuditRecordMapper.xml b/target/classes/mapper/AuditRecordMapper.xml
new file mode 100644
index 0000000..aa6b018
--- /dev/null
+++ b/target/classes/mapper/AuditRecordMapper.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.AuditRecordMapper">
+
+</mapper>
diff --git a/target/classes/mapper/BannedUserMapper.xml b/target/classes/mapper/BannedUserMapper.xml
new file mode 100644
index 0000000..fbdb038
--- /dev/null
+++ b/target/classes/mapper/BannedUserMapper.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.BannedUserMapper">
+
+</mapper>
diff --git a/target/classes/mapper/SuspiciousUserMapper.xml b/target/classes/mapper/SuspiciousUserMapper.xml
new file mode 100644
index 0000000..27851b2
--- /dev/null
+++ b/target/classes/mapper/SuspiciousUserMapper.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.SuspiciousUserMapper">
+
+</mapper>
diff --git a/target/classes/mapper/TorrentMapper.xml b/target/classes/mapper/TorrentMapper.xml
index d5f018e..8e5419b 100644
--- a/target/classes/mapper/TorrentMapper.xml
+++ b/target/classes/mapper/TorrentMapper.xml
@@ -99,6 +99,11 @@
DELETE FROM favorite
WHERE seed_id = #{seedId} AND user_id = #{userId}
</delete>
-
+ <select id="selectMyFavorite" resultType="com.example.myproject.entity.TorrentEntity">
+ SELECT t.*
+ FROM torrent t
+ JOIN favorite f ON t.id = f.seed_id
+ WHERE f.user_id = #{userId}
+ </select>
</mapper>
\ No newline at end of file
diff --git a/uploads/torrents/1749218819331.jpg b/uploads/torrents/1749218819331.jpg
new file mode 100644
index 0000000..4ba04de
--- /dev/null
+++ b/uploads/torrents/1749218819331.jpg
Binary files differ
diff --git a/uploads/torrents/1749219849324.jpg b/uploads/torrents/1749219849324.jpg
new file mode 100644
index 0000000..4ba04de
--- /dev/null
+++ b/uploads/torrents/1749219849324.jpg
Binary files differ
diff --git a/uploads/torrents/1749219884508.jpg b/uploads/torrents/1749219884508.jpg
new file mode 100644
index 0000000..4ba04de
--- /dev/null
+++ b/uploads/torrents/1749219884508.jpg
Binary files differ
diff --git a/uploads/torrents/1749220294853.jpg b/uploads/torrents/1749220294853.jpg
new file mode 100644
index 0000000..4ba04de
--- /dev/null
+++ b/uploads/torrents/1749220294853.jpg
Binary files differ