finish handleAnnounce and fix bugs in torrent upload(using ISO_8859_1)
Change-Id: Ic2da7cef878bb7aa66184f04058bb2db2393190e
diff --git a/src/main/java/com/example/g8backend/controller/TrackerController.java b/src/main/java/com/example/g8backend/controller/TrackerController.java
index 1511c76..4e26abb 100644
--- a/src/main/java/com/example/g8backend/controller/TrackerController.java
+++ b/src/main/java/com/example/g8backend/controller/TrackerController.java
@@ -1,7 +1,9 @@
package com.example.g8backend.controller;
+import com.example.g8backend.dto.AnnounceRequestDTO;
+import com.example.g8backend.dto.AnnounceResponseDTO;
+import jakarta.servlet.http.HttpServletRequest;
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.*;
import com.example.g8backend.service.ITrackerService;
@@ -13,21 +15,41 @@
@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,
+ public ResponseEntity<AnnounceResponseDTO> getAnnouncements(
+ HttpServletRequest request,
+ @RequestParam("info_hash") String infoHash,
+ @RequestParam("peer_id") String peerId,
+ @RequestParam("port") int port,
+ @RequestParam("uploaded") double uploaded,
+ @RequestParam("downloaded") double downloaded,
+ @RequestParam(value = "event", required = false) String event,
+ @RequestParam(value = "left", required = false) Double left,
+ @RequestParam(value = "compact", required = false) Integer compact,
@PathVariable String passkey) {
- return null;
+ AnnounceRequestDTO requestDTO = new AnnounceRequestDTO();
+ requestDTO.setPasskey(passkey);
+ requestDTO.setInfoHash(infoHash);
+ requestDTO.setPeerId(peerId);
+ requestDTO.setPort(port);
+ requestDTO.setUploaded(uploaded);
+ requestDTO.setDownloaded(downloaded);
+ requestDTO.setEvent(event);
+ requestDTO.setLeft(left);
+ requestDTO.setCompact(compact);
+
+ String ipAddress = request.getHeader("X-Forwarded-For");
+ if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getHeader("Proxy-Client-IP");
+ }
+ if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getHeader("WL-Proxy-Client-IP");
+ }
+ if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getRemoteAddr();
+ }
+ requestDTO.setIp(ipAddress.split(",")[0]);
+ return ResponseEntity.ok(trackerService.handleAnnounce(requestDTO));
}
}
diff --git a/src/main/java/com/example/g8backend/dto/AnnounceRequestDTO.java b/src/main/java/com/example/g8backend/dto/AnnounceRequestDTO.java
new file mode 100644
index 0000000..e54f06d
--- /dev/null
+++ b/src/main/java/com/example/g8backend/dto/AnnounceRequestDTO.java
@@ -0,0 +1,17 @@
+package com.example.g8backend.dto;
+
+import lombok.Data;
+
+@Data
+public class AnnounceRequestDTO {
+ private String passkey;
+ private String infoHash;
+ private String peerId;
+ private int port;
+ private double uploaded;
+ private double downloaded;
+ private double left;
+ private Integer compact; // 可选
+ private String event; // 可选
+ private String ip;
+}
diff --git a/src/main/java/com/example/g8backend/dto/AnnounceResponseDTO.java b/src/main/java/com/example/g8backend/dto/AnnounceResponseDTO.java
new file mode 100644
index 0000000..bbb1dc8
--- /dev/null
+++ b/src/main/java/com/example/g8backend/dto/AnnounceResponseDTO.java
@@ -0,0 +1,16 @@
+package com.example.g8backend.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AnnounceResponseDTO {
+ private int interval;
+ private List<Map<String, Object>> peers;
+}
diff --git a/src/main/java/com/example/g8backend/entity/Peer.java b/src/main/java/com/example/g8backend/entity/Peer.java
index b59f7ee..e3e1342 100644
--- a/src/main/java/com/example/g8backend/entity/Peer.java
+++ b/src/main/java/com/example/g8backend/entity/Peer.java
@@ -6,7 +6,7 @@
@Data
@TableName("peers")
public class Peer {
- private Long peerId;
+ private String peerId;
private String info_hash;
private String passkey;
private String ipAddress;
diff --git a/src/main/java/com/example/g8backend/mapper/PeerMapper.java b/src/main/java/com/example/g8backend/mapper/PeerMapper.java
index ce84bbe..f0b9b85 100644
--- a/src/main/java/com/example/g8backend/mapper/PeerMapper.java
+++ b/src/main/java/com/example/g8backend/mapper/PeerMapper.java
@@ -2,7 +2,14 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.g8backend.entity.Peer;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
+@Mapper
public interface PeerMapper extends BaseMapper<Peer> {
-
+ // get peer by primary key(peerId, infoHash, passkey)
+ Peer getPeerByPK(String peerId, String infoHash, String passkey);
+ List<Peer> getPeerByInfoHashAndPeerId(String infoHash, String peerId);
+ void updatePeer(String passkey, String peerId, String info_hash, double uploaded, double downloaded);
}
diff --git a/src/main/java/com/example/g8backend/service/ITrackerService.java b/src/main/java/com/example/g8backend/service/ITrackerService.java
index e522d77..055e317 100644
--- a/src/main/java/com/example/g8backend/service/ITrackerService.java
+++ b/src/main/java/com/example/g8backend/service/ITrackerService.java
@@ -1,6 +1,9 @@
package com.example.g8backend.service;
-public interface ITrackerService {
+import com.example.g8backend.dto.AnnounceRequestDTO;
+import com.example.g8backend.dto.AnnounceResponseDTO;
+public interface ITrackerService {
+ AnnounceResponseDTO handleAnnounce(AnnounceRequestDTO requestDTO);
}
diff --git a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
index bedb94a..199ff2a 100644
--- a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
@@ -17,7 +17,7 @@
@Resource
private TorrentMapper torrentMapper;
- String tracker = "http://127.0.0.1:8080/announce/";
+ String tracker = "http://127.0.0.1:8080/tracker/announce/";
@Override
public Torrent handleTorrentUpload(File file, Long userId, String passkey) throws IOException, IllegalArgumentException {
diff --git a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
index c0dba70..ffb5e65 100644
--- a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
@@ -1,8 +1,88 @@
package com.example.g8backend.service.impl;
+import com.example.g8backend.dto.AnnounceRequestDTO;
+import com.example.g8backend.dto.AnnounceResponseDTO;
+import com.example.g8backend.entity.Peer;
+import com.example.g8backend.mapper.PeerMapper;
import com.example.g8backend.service.ITrackerService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
+import java.util.*;
+
@Service
public class TrackerServiceImpl implements ITrackerService {
+ final int interval = 30;
+
+ @Autowired
+ private PeerMapper peerMapper;
+
+ @Autowired
+ private RedisTemplate<String, Object> redisTemplate;
+
+ @Override
+ public AnnounceResponseDTO handleAnnounce(AnnounceRequestDTO requestDTO) {
+ String passkey = requestDTO.getPasskey();
+ String infoHash = requestDTO.getInfoHash();
+ String peerId = requestDTO.getPeerId();
+ int port = requestDTO.getPort();
+ String ip = requestDTO.getIp();
+ double uploaded = requestDTO.getUploaded();
+ double downloaded = requestDTO.getDownloaded();
+ String event = requestDTO.getEvent();
+
+ String redisKey = "peers:" + infoHash;
+
+ if ("stopped".equalsIgnoreCase(event)) {
+ // 从 Redis 中移除 peer
+ redisTemplate.opsForSet().remove(redisKey, peerId);
+ return new AnnounceResponseDTO(interval, Collections.emptyList());
+ }
+
+ // 插入或更新 peer 信息
+ Peer existingPeer = peerMapper.getPeerByPK(peerId, infoHash, passkey);
+ if (existingPeer == null) {
+ Peer newPeer = new Peer();
+ newPeer.setPeerId(peerId);
+ newPeer.setInfo_hash(infoHash);
+ newPeer.setPasskey(passkey);
+ newPeer.setIpAddress(ip); // TODO: 从 request 获取真实 IP
+ newPeer.setPort(port);
+ newPeer.setUploaded(uploaded);
+ newPeer.setDownloaded(downloaded);
+ peerMapper.insert(newPeer);
+ } else {
+ existingPeer.setUploaded(uploaded);
+ existingPeer.setDownloaded(downloaded);
+ peerMapper.updatePeer(existingPeer.getPasskey(), existingPeer.getPeerId(), existingPeer.getInfo_hash(),
+ existingPeer.getUploaded(), existingPeer.getDownloaded());
+ }
+
+ // 缓存 peer 到 Redis
+ redisTemplate.opsForSet().add(redisKey, peerId);
+
+ // 构造返回 peer 列表
+ List<Map<String, Object>> peerList = new ArrayList<>();
+ Set<Object> peerIds = redisTemplate.opsForSet().members(redisKey);
+ try {
+ assert peerIds != null;
+ for (Object pid : peerIds) {
+ List<Peer> peers = peerMapper.getPeerByInfoHashAndPeerId(infoHash, (String) pid);
+ for (Peer peer : peers) {
+ if (peer != null) {
+ Map<String, Object> peerMap = new HashMap<>();
+ peerMap.put("ip", peer.getIpAddress());
+ peerMap.put("port", peer.getPort());
+ peerList.add(peerMap);
+ }
+ }
+ }
+ } catch (Exception e) {
+ return new AnnounceResponseDTO(interval, Collections.emptyList());
+ }
+
+ return new AnnounceResponseDTO(interval, peerList);
+ }
+
}
diff --git a/src/main/java/com/example/g8backend/util/TorrentUtil.java b/src/main/java/com/example/g8backend/util/TorrentUtil.java
index e8360bc..b7390d6 100644
--- a/src/main/java/com/example/g8backend/util/TorrentUtil.java
+++ b/src/main/java/com/example/g8backend/util/TorrentUtil.java
@@ -4,12 +4,13 @@
import com.dampcake.bencode.Type;
import java.io.*;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
public class TorrentUtil {
- private static final Bencode bencode = new Bencode();
+ private static final Bencode bencode = new Bencode(StandardCharsets.ISO_8859_1);
public static byte[] injectTracker(File torrentFile, String trackerUrl) throws IOException {
byte[] fileBytes = readBytes(torrentFile);
@@ -17,7 +18,9 @@
Map<String, Object> torrentMap = bencode.decode(fileBytes, Type.DICTIONARY);
// trackerUrl: ip:port + /announce / {passkey}
- torrentMap.put("announce", trackerUrl);
+ byte[] ISO_trackerUrlByte = trackerUrl.getBytes(StandardCharsets.ISO_8859_1);
+ String ISO_trackerUrl = new String(ISO_trackerUrlByte, StandardCharsets.ISO_8859_1);
+ torrentMap.put("announce", ISO_trackerUrl); // ISO-8859-1
return bencode.encode(torrentMap);
}