tracker服务器以及torrent上传下载逻辑&依赖&种子文件
Change-Id: I8cb04f663faf1f4d0fadb0c4585ba12bc0dd929c
diff --git a/src/main/java/edu/bjtu/groupone/backend/api/TorrentController.java b/src/main/java/edu/bjtu/groupone/backend/api/TorrentController.java
new file mode 100644
index 0000000..e5f1aa4
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/api/TorrentController.java
@@ -0,0 +1,53 @@
+package edu.bjtu.groupone.backend.api;
+
+import edu.bjtu.groupone.backend.domain.entity.Torrent;
+import edu.bjtu.groupone.backend.service.TorrentService;
+import edu.bjtu.groupone.backend.utils.GetTokenUserId;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@RestController
+@RequestMapping({"/api/torrents"})
+public class TorrentController {
+ @Autowired
+ private TorrentService torrentService;
+
+ public TorrentController() {
+ }
+
+ @PostMapping({"/upload"})
+ public ResponseEntity<?> uploadTorrent(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
+ String uidStr = GetTokenUserId.getUserId(request);
+ if (uidStr == null) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token无效或缺失");
+ } else {
+ try {
+ Long userId = Long.parseLong(uidStr);
+ Torrent saved = this.torrentService.uploadTorrent(file, userId);
+ return ResponseEntity.ok(saved);
+ } catch (Exception var6) {
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传失败:" + var6.getMessage());
+ }
+ }
+ }
+
+ @GetMapping({"/download/{infoHash}"})
+ public ResponseEntity<Resource> downloadTorrent(@PathVariable String infoHash) {
+ try {
+ Resource resource = this.torrentService.downloadTorrent(infoHash);
+ return ((ResponseEntity.BodyBuilder)ResponseEntity.ok().header("Content-Disposition", new String[]{"attachment; filename=\"" + infoHash + ".torrent\""})).body(resource);
+ } catch (Exception var3) {
+ return ResponseEntity.notFound().build();
+ }
+ }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/config/TrackerStarter.java b/src/main/java/edu/bjtu/groupone/backend/config/TrackerStarter.java
new file mode 100644
index 0000000..6ae8be7
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/config/TrackerStarter.java
@@ -0,0 +1,50 @@
+package edu.bjtu.groupone.backend.config;
+
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.turn.ttorrent.tracker.Tracker;
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.security.NoSuchAlgorithmException;
+import java.util.Objects;
+import javax.annotation.PostConstruct;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TrackerStarter {
+ private Tracker tracker;
+
+ public TrackerStarter() {
+ }
+
+ @PostConstruct
+ public void startTracker() throws Exception {
+ InetSocketAddress address = new InetSocketAddress("0.0.0.0", 6969);
+ this.tracker = new Tracker(address);
+ this.tracker.start();
+ System.out.println("Tracker started on http://localhost:6969/announce");
+ }
+
+ @Scheduled(
+ fixedRate = 60000L
+ )
+ public void scanTorrentDirectory() throws IOException, NoSuchAlgorithmException {
+ File torrentDir = new File("C:\\Users\\wangy\\Desktop\\GroupOne-Back-End(2)\\GroupOne-Back-End\\torrents");
+ if (torrentDir.exists() && torrentDir.isDirectory()) {
+ File[] var2 = (File[])Objects.requireNonNull(torrentDir.listFiles());
+ int var3 = var2.length;
+
+ for(int var4 = 0; var4 < var3; ++var4) {
+ File file = var2[var4];
+ if (file.getName().endsWith(".torrent")) {
+ this.tracker.announce(TrackedTorrent.load(file));
+ System.out.println("Loaded torrent: " + file.getName());
+ }
+ }
+ } else {
+ System.out.println("Torrent directory not found: " + torrentDir.getAbsolutePath());
+ }
+
+ }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/domain/entity/Torrent.java b/src/main/java/edu/bjtu/groupone/backend/domain/entity/Torrent.java
new file mode 100644
index 0000000..0564b50
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/domain/entity/Torrent.java
@@ -0,0 +1,258 @@
+package edu.bjtu.groupone.backend.domain.entity;
+
+public class Torrent {
+ private Long id;
+ private String name;
+ private String infoHash;
+ private String filePath;
+ private Long size;
+ private Long uploaderId;
+ private Boolean isFreeleech;
+ private Double uploadMultiplier;
+ private String uploadedAt;
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getInfoHash() {
+ return this.infoHash;
+ }
+
+ public String getFilePath() {
+ return this.filePath;
+ }
+
+ public Long getSize() {
+ return this.size;
+ }
+
+ public Long getUploaderId() {
+ return this.uploaderId;
+ }
+
+ public Boolean getIsFreeleech() {
+ return this.isFreeleech;
+ }
+
+ public Double getUploadMultiplier() {
+ return this.uploadMultiplier;
+ }
+
+ public String getUploadedAt() {
+ return this.uploadedAt;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public void setInfoHash(final String infoHash) {
+ this.infoHash = infoHash;
+ }
+
+ public void setFilePath(final String filePath) {
+ this.filePath = filePath;
+ }
+
+ public void setSize(final Long size) {
+ this.size = size;
+ }
+
+ public void setUploaderId(final Long uploaderId) {
+ this.uploaderId = uploaderId;
+ }
+
+ public void setIsFreeleech(final Boolean isFreeleech) {
+ this.isFreeleech = isFreeleech;
+ }
+
+ public void setUploadMultiplier(final Double uploadMultiplier) {
+ this.uploadMultiplier = uploadMultiplier;
+ }
+
+ public void setUploadedAt(final String uploadedAt) {
+ this.uploadedAt = uploadedAt;
+ }
+
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ } else if (!(o instanceof Torrent)) {
+ return false;
+ } else {
+ Torrent other = (Torrent)o;
+ if (!other.canEqual(this)) {
+ return false;
+ } else {
+ label119: {
+ Object this$id = this.getId();
+ Object other$id = other.getId();
+ if (this$id == null) {
+ if (other$id == null) {
+ break label119;
+ }
+ } else if (this$id.equals(other$id)) {
+ break label119;
+ }
+
+ return false;
+ }
+
+ Object this$size = this.getSize();
+ Object other$size = other.getSize();
+ if (this$size == null) {
+ if (other$size != null) {
+ return false;
+ }
+ } else if (!this$size.equals(other$size)) {
+ return false;
+ }
+
+ label105: {
+ Object this$uploaderId = this.getUploaderId();
+ Object other$uploaderId = other.getUploaderId();
+ if (this$uploaderId == null) {
+ if (other$uploaderId == null) {
+ break label105;
+ }
+ } else if (this$uploaderId.equals(other$uploaderId)) {
+ break label105;
+ }
+
+ return false;
+ }
+
+ Object this$isFreeleech = this.getIsFreeleech();
+ Object other$isFreeleech = other.getIsFreeleech();
+ if (this$isFreeleech == null) {
+ if (other$isFreeleech != null) {
+ return false;
+ }
+ } else if (!this$isFreeleech.equals(other$isFreeleech)) {
+ return false;
+ }
+
+ label91: {
+ Object this$uploadMultiplier = this.getUploadMultiplier();
+ Object other$uploadMultiplier = other.getUploadMultiplier();
+ if (this$uploadMultiplier == null) {
+ if (other$uploadMultiplier == null) {
+ break label91;
+ }
+ } else if (this$uploadMultiplier.equals(other$uploadMultiplier)) {
+ break label91;
+ }
+
+ return false;
+ }
+
+ Object this$name = this.getName();
+ Object other$name = other.getName();
+ if (this$name == null) {
+ if (other$name != null) {
+ return false;
+ }
+ } else if (!this$name.equals(other$name)) {
+ return false;
+ }
+
+ label77: {
+ Object this$infoHash = this.getInfoHash();
+ Object other$infoHash = other.getInfoHash();
+ if (this$infoHash == null) {
+ if (other$infoHash == null) {
+ break label77;
+ }
+ } else if (this$infoHash.equals(other$infoHash)) {
+ break label77;
+ }
+
+ return false;
+ }
+
+ label70: {
+ Object this$filePath = this.getFilePath();
+ Object other$filePath = other.getFilePath();
+ if (this$filePath == null) {
+ if (other$filePath == null) {
+ break label70;
+ }
+ } else if (this$filePath.equals(other$filePath)) {
+ break label70;
+ }
+
+ return false;
+ }
+
+ Object this$uploadedAt = this.getUploadedAt();
+ Object other$uploadedAt = other.getUploadedAt();
+ if (this$uploadedAt == null) {
+ if (other$uploadedAt != null) {
+ return false;
+ }
+ } else if (!this$uploadedAt.equals(other$uploadedAt)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof Torrent;
+ }
+
+ public int hashCode() {
+ boolean PRIME = true;
+ int result = 1;
+ Object $id = this.getId();
+ result = result * 59 + ($id == null ? 43 : $id.hashCode());
+ Object $size = this.getSize();
+ result = result * 59 + ($size == null ? 43 : $size.hashCode());
+ Object $uploaderId = this.getUploaderId();
+ result = result * 59 + ($uploaderId == null ? 43 : $uploaderId.hashCode());
+ Object $isFreeleech = this.getIsFreeleech();
+ result = result * 59 + ($isFreeleech == null ? 43 : $isFreeleech.hashCode());
+ Object $uploadMultiplier = this.getUploadMultiplier();
+ result = result * 59 + ($uploadMultiplier == null ? 43 : $uploadMultiplier.hashCode());
+ Object $name = this.getName();
+ result = result * 59 + ($name == null ? 43 : $name.hashCode());
+ Object $infoHash = this.getInfoHash();
+ result = result * 59 + ($infoHash == null ? 43 : $infoHash.hashCode());
+ Object $filePath = this.getFilePath();
+ result = result * 59 + ($filePath == null ? 43 : $filePath.hashCode());
+ Object $uploadedAt = this.getUploadedAt();
+ result = result * 59 + ($uploadedAt == null ? 43 : $uploadedAt.hashCode());
+ return result;
+ }
+
+ public String toString() {
+ Long var10000 = this.getId();
+ return "Torrent(id=" + var10000 + ", name=" + this.getName() + ", infoHash=" + this.getInfoHash() + ", filePath=" + this.getFilePath() + ", size=" + this.getSize() + ", uploaderId=" + this.getUploaderId() + ", isFreeleech=" + this.getIsFreeleech() + ", uploadMultiplier=" + this.getUploadMultiplier() + ", uploadedAt=" + this.getUploadedAt() + ")";
+ }
+
+ public Torrent(final Long id, final String name, final String infoHash, final String filePath, final Long size, final Long uploaderId, final Boolean isFreeleech, final Double uploadMultiplier, final String uploadedAt) {
+ this.id = id;
+ this.name = name;
+ this.infoHash = infoHash;
+ this.filePath = filePath;
+ this.size = size;
+ this.uploaderId = uploaderId;
+ this.isFreeleech = isFreeleech;
+ this.uploadMultiplier = uploadMultiplier;
+ this.uploadedAt = uploadedAt;
+ }
+
+ public Torrent() {
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/mapper/TorrentMapper.java b/src/main/java/edu/bjtu/groupone/backend/mapper/TorrentMapper.java
new file mode 100644
index 0000000..a00d3cf
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/mapper/TorrentMapper.java
@@ -0,0 +1,9 @@
+package edu.bjtu.groupone.backend.mapper;
+
+import edu.bjtu.groupone.backend.domain.entity.Torrent;
+
+public interface TorrentMapper {
+ void insertTorrent(Torrent torrent);
+
+ Torrent selectByInfoHash(String infoHash);
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java b/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java
index 3e14ee4..ba0763e 100644
--- a/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java
+++ b/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java
@@ -42,4 +42,7 @@
@Select("SELECT * FROM user")
List<User> selectAllUsers();
+
+ @Select({"SELECT * FROM user WHERE userid = #{id}"})
+ User selectById(@Param("id") Long id);
}
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/TorrentService.java b/src/main/java/edu/bjtu/groupone/backend/service/TorrentService.java
new file mode 100644
index 0000000..5a00de5
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/TorrentService.java
@@ -0,0 +1,11 @@
+package edu.bjtu.groupone.backend.service;
+
+import edu.bjtu.groupone.backend.domain.entity.Torrent;
+import org.springframework.core.io.Resource;
+import org.springframework.web.multipart.MultipartFile;
+
+public interface TorrentService {
+ Torrent uploadTorrent(MultipartFile file, Long userId) throws Exception;
+
+ Resource downloadTorrent(String infoHash) throws Exception;
+}
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/impl/TorrentServiceImpl.java b/src/main/java/edu/bjtu/groupone/backend/service/impl/TorrentServiceImpl.java
new file mode 100644
index 0000000..a1dac1f
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/impl/TorrentServiceImpl.java
@@ -0,0 +1,68 @@
+package edu.bjtu.groupone.backend.service.impl;
+
+import edu.bjtu.groupone.backend.mapper.TorrentMapper;
+import edu.bjtu.groupone.backend.mapper.UserMapper;
+import edu.bjtu.groupone.backend.domain.entity.Torrent;
+import edu.bjtu.groupone.backend.domain.entity.User;
+import edu.bjtu.groupone.backend.service.TorrentService;
+import edu.bjtu.groupone.backend.utils.TorrentParserUtil;
+import java.io.File;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+@Service
+public class TorrentServiceImpl implements TorrentService {
+ @Value("${torrent.storage.path}")
+ private String storagePath;
+ @Autowired
+ private TorrentMapper torrentMapper;
+ @Autowired
+ private UserMapper userMapper;
+
+ public TorrentServiceImpl() {
+ }
+
+ public Torrent uploadTorrent(MultipartFile file, Long userId) throws Exception {
+ Map<String, Object> meta = TorrentParserUtil.parseTorrent(file.getInputStream());
+ String infoHash = (String)meta.get("infoHash");
+ String name = (String)meta.get("name");
+ Long size = (Long)meta.get("length");
+ String pathToUse = this.storagePath != null && !this.storagePath.isBlank() ? this.storagePath : System.getProperty("java.io.tmpdir");
+ File dir = new File(pathToUse);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ String savePath = pathToUse + File.separator + infoHash + ".torrent";
+ File dest = new File(savePath);
+ file.transferTo(dest);
+ User uploader = this.userMapper.selectById(userId);
+ Torrent torrent = new Torrent();
+ torrent.setInfoHash(infoHash);
+ torrent.setName(name);
+ torrent.setFilePath(savePath);
+ torrent.setSize(size);
+ torrent.setUploaderId((long)uploader.getUserId());
+ this.torrentMapper.insertTorrent(torrent);
+ return torrent;
+ }
+
+ public Resource downloadTorrent(String infoHash) throws Exception {
+ Torrent torrent = this.torrentMapper.selectByInfoHash(infoHash);
+ if (torrent == null) {
+ throw new IllegalArgumentException("种子不存在");
+ } else {
+ File file = new File(torrent.getFilePath());
+ if (!file.exists()) {
+ throw new IllegalStateException("种子文件不存在");
+ } else {
+ return new FileSystemResource(file);
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/TorrentParserUtil.java b/src/main/java/edu/bjtu/groupone/backend/utils/TorrentParserUtil.java
new file mode 100644
index 0000000..fdc14be
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/TorrentParserUtil.java
@@ -0,0 +1,32 @@
+package edu.bjtu.groupone.backend.utils;
+
+import com.turn.ttorrent.bcodec.BDecoder;
+import com.turn.ttorrent.bcodec.BEValue;
+import com.turn.ttorrent.bcodec.BEncoder;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.codec.digest.DigestUtils;
+
+public class TorrentParserUtil {
+ public TorrentParserUtil() {
+ }
+
+ public static Map<String, Object> parseTorrent(InputStream inputStream) throws Exception {
+ Map<String, Object> result = new HashMap();
+ BEValue rootBev = BDecoder.bdecode(inputStream);
+ Map<String, BEValue> root = rootBev.getMap();
+ Map<String, BEValue> info = ((BEValue)root.get("info")).getMap();
+ String name = ((BEValue)info.get("name")).getString();
+ long length = info.containsKey("length") ? ((BEValue)info.get("length")).getLong() : -1L;
+ ByteBuffer buffer = BEncoder.bencode(info);
+ byte[] encodedInfo = new byte[buffer.remaining()];
+ buffer.get(encodedInfo);
+ String infoHash = DigestUtils.sha1Hex(encodedInfo);
+ result.put("infoHash", infoHash);
+ result.put("name", name);
+ result.put("length", length);
+ return result;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 668bc3b..165698e 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,15 +1,14 @@
# ??MySQL??
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-spring.datasource.url=jdbc:mysql://localhost:3306/llksh\
+spring.datasource.url=jdbc:mysql://rm-cn-qzy4ah2qt0008vfo.rwlb.rds.aliyuncs.com:3306/smart_course_platform\
?useSSL=false\
&serverTimezone=Asia/Shanghai\
&characterEncoding=utf8\
&allowPublicKeyRetrieval=true
-spring.datasource.username=root
-spring.datasource.password=wuxiaorui123
+spring.datasource.username=wangy
+spring.datasource.password=Wyt2005011600
# src/main/resources/application.properties
-spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
# ???????????
spring.datasource.hikari.maximum-pool-size=10
@@ -26,4 +25,6 @@
mybatis.type-aliases-package=edu.bjtu.groupone.backend.domain.entity
# ????????
-mybatis.mapper-locations=classpath:mapper/*.xml
\ No newline at end of file
+mybatis.mapper-locations=classpath:mapper/*.xml
+
+torrent.storage.path=./torrent
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 2d79bf4..ae77276 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -15,9 +15,9 @@
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3306/llksh?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
- username: root
- password: "wuxiaorui123"
+ url: jdbc:mysql://rm-cn-qzy4ah2qt0008vfo.rwlb.rds.aliyuncs.com:3306/smart_course_platform?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
+ username: wangy
+ password: "Wyt2005011600"
hikari:
maximum-pool-size: 10
minimum-idle: 5