Merge branch 'wyh'
Change-Id: I55d5826b58de971650ef431403ab7830743fac28
diff --git a/.gitignore b/.gitignore
index 549e00a..d9e9af4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,6 @@
### VS Code ###
.vscode/
+
+### Upload ###
+./uploaded-torrents/
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/controller/AuthController.java b/src/main/java/com/example/g8backend/controller/AuthController.java
index 4b3be4b..2f36500 100644
--- a/src/main/java/com/example/g8backend/controller/AuthController.java
+++ b/src/main/java/com/example/g8backend/controller/AuthController.java
@@ -63,6 +63,9 @@
user.setUserName(registerDTO.getUserName());
user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
user.setEmail(registerDTO.getEmail());
+
+ // passkey 用于在客户端发送announce请求时获取用户信息
+ user.setPasskey(UUID.randomUUID().toString().replace("-", ""));
userService.registerUser(user);
return ResponseEntity.ok("注册成功");
diff --git a/src/main/java/com/example/g8backend/controller/TorrentController.java b/src/main/java/com/example/g8backend/controller/TorrentController.java
new file mode 100644
index 0000000..bec187e
--- /dev/null
+++ b/src/main/java/com/example/g8backend/controller/TorrentController.java
@@ -0,0 +1,46 @@
+package com.example.g8backend.controller;
+
+import com.example.g8backend.entity.User;
+import com.example.g8backend.service.IUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.example.g8backend.service.ITorrentService;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+
+@RestController
+@RequestMapping("/torrent")
+public class TorrentController {
+ @Autowired
+ private ITorrentService torrentService;
+
+ @Autowired
+ private IUserService userService;
+
+ @RequestMapping("/upload")
+ public ResponseEntity<?> handleTorrentUpload(@RequestParam("file") MultipartFile multipartFile) throws IOException {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ long userId = (long) authentication.getPrincipal();
+
+ User user = userService.getById(userId);
+ String passkey = user.getPasskey();
+
+ File tempFile = File.createTempFile("upload-", ".torrent");
+ multipartFile.transferTo(tempFile);
+
+ torrentService.handleTorrentUpload(tempFile, userId, passkey);
+
+ // 删除临时文件
+ if(!tempFile.delete()){
+ throw new IOException("Failed to delete temporary file: " + tempFile.getAbsolutePath());
+ }
+ return ResponseEntity.ok("种子上传成功");
+ }
+}
diff --git a/src/main/java/com/example/g8backend/controller/TrackerController.java b/src/main/java/com/example/g8backend/controller/TrackerController.java
index 7ee9e5a..d6cc957 100644
--- a/src/main/java/com/example/g8backend/controller/TrackerController.java
+++ b/src/main/java/com/example/g8backend/controller/TrackerController.java
@@ -1,15 +1,33 @@
package com.example.g8backend.controller;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
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.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import com.example.g8backend.service.ITrackerService;
@RestController
-@RequestMapping("/announce")
+@RequestMapping("/tracker")
public class TrackerController {
+ @Autowired
+ private ITrackerService trackerService;
+
+ @Autowired
+ private RedisTemplate<String, Object> redisTemplate;
+
+ @GetMapping("/announce/{passkey}}")
+ public ResponseEntity<?> getAnnouncements(
+ @RequestParam(name = "info_hash") String infoHash,
+ @RequestParam(name = "peer_id") String peerId,
+ @RequestParam(name = "port") int port,
+ @RequestParam(name = "uploaded") long uploaded,
+ @RequestParam(name = "downloaded") long downloaded,
+ @RequestParam(name = "left") long left,
+ @RequestParam(name = "compact", required = false) int compact,
+ @RequestParam(name = "event", required = false) String event,
+ @PathVariable String passkey) {
+
+ return null;
+ }
}
diff --git a/src/main/java/com/example/g8backend/entity/Peer.java b/src/main/java/com/example/g8backend/entity/Peer.java
index 43302ce..b59f7ee 100644
--- a/src/main/java/com/example/g8backend/entity/Peer.java
+++ b/src/main/java/com/example/g8backend/entity/Peer.java
@@ -1,7 +1,5 @@
package com.example.g8backend.entity;
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -10,7 +8,7 @@
public class Peer {
private Long peerId;
private String info_hash;
- private Long userId; // passkey from announce
+ private String passkey;
private String ipAddress;
private Integer port;
private Double uploaded;
diff --git a/src/main/java/com/example/g8backend/entity/Torrent.java b/src/main/java/com/example/g8backend/entity/Torrent.java
index b1e1985..cb9a4eb 100644
--- a/src/main/java/com/example/g8backend/entity/Torrent.java
+++ b/src/main/java/com/example/g8backend/entity/Torrent.java
@@ -12,15 +12,12 @@
@TableName("torrents")
public class Torrent {
@TableId(type = IdType.AUTO)
- private Integer torrentId;
+ private Long torrentId;
private Long userId;
private String torrentName;
private String infoHash;
private Double fileSize;
- @TableField("created_at")
- private Timestamp createdAt;
-
@Override
public String toString() {
return "Torrent{" +
@@ -29,7 +26,6 @@
", torrentName='" + torrentName + '\'' +
", infoHash='" + infoHash + '\'' +
", fileSize=" + fileSize +
- ", createdAt=" + createdAt +
'}';
}
}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/entity/User.java b/src/main/java/com/example/g8backend/entity/User.java
index 8e28628..7ba8e03 100644
--- a/src/main/java/com/example/g8backend/entity/User.java
+++ b/src/main/java/com/example/g8backend/entity/User.java
@@ -11,6 +11,7 @@
@TableId(type = IdType.AUTO)
private Long userId;
+ private String passkey;
private String password;
private String userName;
private String email;
diff --git a/src/main/java/com/example/g8backend/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/g8backend/filter/JwtAuthenticationFilter.java
index d251424..42c7e5b 100644
--- a/src/main/java/com/example/g8backend/filter/JwtAuthenticationFilter.java
+++ b/src/main/java/com/example/g8backend/filter/JwtAuthenticationFilter.java
@@ -27,7 +27,7 @@
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String path = request.getServletPath();
- if (path.startsWith("/auth")) {
+ if (path.startsWith("/auth") || path.startsWith("/tracker")) {
filterChain.doFilter(request, response);
return;
}
diff --git a/src/main/java/com/example/g8backend/mapper/PeerMapper.java b/src/main/java/com/example/g8backend/mapper/PeerMapper.java
new file mode 100644
index 0000000..ce84bbe
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/PeerMapper.java
@@ -0,0 +1,8 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.Peer;
+
+public interface PeerMapper extends BaseMapper<Peer> {
+
+}
diff --git a/src/main/java/com/example/g8backend/mapper/TorrentMapper.java b/src/main/java/com/example/g8backend/mapper/TorrentMapper.java
new file mode 100644
index 0000000..f78a68b
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/TorrentMapper.java
@@ -0,0 +1,16 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.Torrent;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface TorrentMapper extends BaseMapper<Torrent> {
+ int insertTorrent (@Param("userId") Long userId,
+ @Param("torrentName") String torrentName,
+ @Param("infoHash") String infoHash,
+ @Param("fileSize") Double fileSize);
+ Torrent getTorrentByInfoHash (@Param("infoHash") String infoHash);
+ Torrent getTorrentByTorrentId (@Param("torrentId") Long torrentId);
+}
diff --git a/src/main/java/com/example/g8backend/mapper/UserMapper.java b/src/main/java/com/example/g8backend/mapper/UserMapper.java
index b389eb7..0a9e562 100644
--- a/src/main/java/com/example/g8backend/mapper/UserMapper.java
+++ b/src/main/java/com/example/g8backend/mapper/UserMapper.java
@@ -9,4 +9,5 @@
public interface UserMapper extends BaseMapper<User> {
User getUserByName(@Param("userName") String userName);
User getUserByEmail(@Param("email") String email);
+ User getUserByPasskey(@Param("passkey") String passkey);
}
diff --git a/src/main/java/com/example/g8backend/service/ITorrentService.java b/src/main/java/com/example/g8backend/service/ITorrentService.java
new file mode 100644
index 0000000..5e48dc4
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/ITorrentService.java
@@ -0,0 +1,13 @@
+package com.example.g8backend.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.example.g8backend.entity.Torrent;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface ITorrentService extends IService<Torrent> {
+ Torrent handleTorrentUpload(File file, Long userId, String passkey) throws IOException;
+ Torrent findByInfoHash(String infoHash);
+ Torrent findByTorrentId(Long torrentId);
+}
diff --git a/src/main/java/com/example/g8backend/service/IUserService.java b/src/main/java/com/example/g8backend/service/IUserService.java
index 2fb4fc9..f407c37 100644
--- a/src/main/java/com/example/g8backend/service/IUserService.java
+++ b/src/main/java/com/example/g8backend/service/IUserService.java
@@ -7,5 +7,6 @@
public interface IUserService extends IService<User> {
User getUserByName(@Param("name") String name);
User getUserByEmail(@Param("email") String email);
+ User getUserByPasskey(@Param("passkey") String passkey);
void registerUser(User user);;
}
diff --git a/src/main/java/com/example/g8backend/service/TrackerServiceImpl.java b/src/main/java/com/example/g8backend/service/TrackerServiceImpl.java
deleted file mode 100644
index ae12f70..0000000
--- a/src/main/java/com/example/g8backend/service/TrackerServiceImpl.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.example.g8backend.service;
-
-import org.springframework.stereotype.Service;
-
-@Service
-public class TrackerServiceImpl implements ITrackerService {
-}
diff --git a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
new file mode 100644
index 0000000..03185ba
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
@@ -0,0 +1,67 @@
+package com.example.g8backend.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.g8backend.entity.Torrent;
+import com.example.g8backend.mapper.TorrentMapper;
+import com.example.g8backend.service.ITorrentService;
+import com.example.g8backend.util.TorrentUtil;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@Service
+public class TorrentServiceImpl extends ServiceImpl<TorrentMapper, Torrent> implements ITorrentService {
+ @Resource
+ private TorrentMapper torrentMapper;
+
+ @Override
+ public Torrent handleTorrentUpload(File file, Long userId, String passkey) throws IOException{
+ String tracker = "http://127.0.0.1:8080/announce/" + passkey;
+
+ // 修改 announce 字段
+ byte[] modifiedBytes = TorrentUtil.injectTracker(file, tracker);
+
+ // 计算 info_hash
+ String infoHash = TorrentUtil.getInfoHash(file);
+
+ // 文件大小(以MB为单位)
+ double fileSize = file.length() / 1024.0 / 1024.0;
+
+ // 保存新的种子文件(可选)
+ File outputDir = new File("uploaded-torrents");
+ if (!outputDir.exists()) {
+ if (!outputDir.mkdirs()){
+ throw new IOException("Failed to create directory: " + outputDir.getAbsolutePath());
+ }
+ }
+
+ File savedFile = new File(outputDir, file.getName());
+ try (FileOutputStream fos = new FileOutputStream(savedFile)) {
+ fos.write(modifiedBytes);
+ }
+
+ // 插入数据库
+ torrentMapper.insertTorrent(userId, file.getName(), infoHash, fileSize);
+
+ // 构建返回实体
+ Torrent torrent = new Torrent();
+ torrent.setUserId(userId);
+ torrent.setTorrentName(file.getName());
+ torrent.setInfoHash(infoHash);
+ torrent.setFileSize(fileSize);
+ return torrent;
+ }
+
+ @Override
+ public Torrent findByInfoHash(String infoHash){
+ return torrentMapper.getTorrentByInfoHash(infoHash);
+ }
+
+ @Override
+ public Torrent findByTorrentId(Long torrentId){
+ return torrentMapper.getTorrentByTorrentId(torrentId);
+ }
+}
diff --git a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
new file mode 100644
index 0000000..c0dba70
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
@@ -0,0 +1,8 @@
+package com.example.g8backend.service.impl;
+
+import com.example.g8backend.service.ITrackerService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TrackerServiceImpl implements ITrackerService {
+}
diff --git a/src/main/java/com/example/g8backend/service/UserServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java
similarity index 76%
rename from src/main/java/com/example/g8backend/service/UserServiceImpl.java
rename to src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java
index 567c5f3..3f3357c 100644
--- a/src/main/java/com/example/g8backend/service/UserServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java
@@ -1,11 +1,10 @@
-package com.example.g8backend.service;
+package com.example.g8backend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.g8backend.entity.User;
import com.example.g8backend.mapper.UserMapper;
+import com.example.g8backend.service.IUserService;
import jakarta.annotation.Resource;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -14,9 +13,6 @@
@Resource
private UserMapper userMapper; // 手动注入 UserMapper
- @Autowired
- private PasswordEncoder passwordEncoder;
-
@Override
public User getUserByName(String name) { return userMapper.getUserByName(name);} // 调用 UserMapper 的自定义 SQL
@@ -24,5 +20,8 @@
public User getUserByEmail(String email) { return userMapper.getUserByEmail(email);}
@Override
+ public User getUserByPasskey(String passkey) { return userMapper.getUserByPasskey(passkey);}
+
+ @Override
public void registerUser(User user) {userMapper.insert(user);}
}
diff --git a/src/main/java/com/example/g8backend/util/TorrentUtil.java b/src/main/java/com/example/g8backend/util/TorrentUtil.java
index 37ce7da..f644fff 100644
--- a/src/main/java/com/example/g8backend/util/TorrentUtil.java
+++ b/src/main/java/com/example/g8backend/util/TorrentUtil.java
@@ -1,4 +1,66 @@
package com.example.g8backend.util;
+import com.dampcake.bencode.Bencode;
+import com.dampcake.bencode.Type;
+
+import java.io.*;
+import java.security.MessageDigest;
+import java.util.Map;
+
public class TorrentUtil {
+
+ private static final Bencode bencode = new Bencode();
+
+ public static byte[] injectTracker(File torrentFile, String trackerUrl) throws IOException {
+ byte[] fileBytes = readBytes(torrentFile);
+
+ Map<String, Object> torrentMap = bencode.decode(fileBytes, Type.DICTIONARY);
+
+ // trackerUrl: ip:port + /announce / {passkey}
+ torrentMap.put("announce", trackerUrl);
+
+ return bencode.encode(torrentMap);
+ }
+
+ public static String getInfoHash(File torrentFile) throws IOException {
+ byte[] fileBytes = readBytes(torrentFile);
+ Map<String, Object> torrentMap = bencode.decode(fileBytes, Type.DICTIONARY);
+
+ @SuppressWarnings("unchecked")
+ Map<String, Object> info = (Map<String, Object>) torrentMap.get("info");
+
+ // 对 info 字典重新编码
+ byte[] infoBytes = bencode.encode(info);
+
+ // 计算 SHA-1 hash
+ MessageDigest sha1;
+ try {
+ sha1 = MessageDigest.getInstance("SHA-1");
+ } catch (Exception e) {
+ throw new RuntimeException("SHA-1 not supported", e);
+ }
+
+ byte[] hash = sha1.digest(infoBytes);
+ return bytesToHex(hash);
+ }
+
+ public static void saveToFile(byte[] data, File outputFile) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(outputFile)) {
+ fos.write(data);
+ }
+ }
+
+ private static byte[] readBytes(File file) throws IOException {
+ try (InputStream in = new FileInputStream(file)) {
+ return in.readAllBytes();
+ }
+ }
+
+ private static String bytesToHex(byte[] hash) {
+ StringBuilder hex = new StringBuilder();
+ for (byte b : hash) {
+ hex.append(String.format("%02x", b));
+ }
+ return hex.toString();
+ }
}
diff --git a/src/main/resources/mapper/TorrentMapper.xml b/src/main/resources/mapper/TorrentMapper.xml
new file mode 100644
index 0000000..9b53d29
--- /dev/null
+++ b/src/main/resources/mapper/TorrentMapper.xml
@@ -0,0 +1,32 @@
+<?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.g8backend.mapper.TorrentMapper">
+ <insert id="insertTorrent" >
+ INSERT INTO torrents (user_id, torrent_name, info_hash, file_size)
+ VALUES (#{userId}, #{torrentName}, UNHEX(#{infoHash}), #{fileSize})
+ </insert>
+
+ <select id="getTorrentByInfoHash" resultType="com.example.g8backend.entity.Torrent">
+ SELECT
+ torrent_id,
+ user_id,
+ torrent_name,
+ HEX(info_hash) AS infoHash,
+ file_size
+ FROM torrents
+ WHERE info_hash = UNHEX(#{infoHash})
+ </select>
+
+ <select id="getTorrentByTorrentId" resultType="com.example.g8backend.entity.Torrent">
+ SELECT
+ torrent_id,
+ user_id,
+ torrent_name,
+ HEX(info_hash) AS infoHash,
+ file_size
+ FROM torrents
+ WHERE torrent_id = #{torrentId}
+ </select>
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
index 314c087..9627647 100644
--- a/src/main/resources/mapper/UserMapper.xml
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -9,4 +9,7 @@
<select id="getUserByEmail" resultType="com.example.g8backend.entity.User">
SELECT * FROM users WHERE email = #{email}
</select>
+ <select id="getUserByPasskey" resultType="com.example.g8backend.entity.User">
+ SELECT * FROM users WHERE passkey = #{passkey}
+ </select>
</mapper>
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 2018117..365f9e7 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -2,7 +2,8 @@
user_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
- email VARCHAR(255) NOT NULL UNIQUE
+ email VARCHAR(255) NOT NULL UNIQUE,
+ passkey VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `torrents` (
@@ -15,7 +16,7 @@
);
CREATE TABLE IF NOT EXISTS `peers` (
- user_id INT NOT NULL,
+ passkey VARCHAR(255) NOT NULL,
info_hash BINARY(20) NOT NULL,
peer_id VARCHAR(20) NOT NULL,
ip_address VARCHAR(128) NOT NULL,
@@ -23,6 +24,6 @@
uploaded FLOAT NOT NULL,
downloaded FLOAT NOT NULL,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(user_id),
- PRIMARY KEY (user_id, info_hash, peer_id)
+ FOREIGN KEY (passkey) REFERENCES users(passkey),
+ PRIMARY KEY (passkey, info_hash, peer_id)
);
diff --git a/uploaded-torrents/upload-1263176031835183570.torrent b/uploaded-torrents/upload-1263176031835183570.torrent
new file mode 100644
index 0000000..8634e79
--- /dev/null
+++ b/uploaded-torrents/upload-1263176031835183570.torrent
Binary files differ