Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 1 | package com.pt.service; |
| 2 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 3 | import com.pt.constant.Constants; |
| 4 | import com.pt.entity.PeerInfoEntity; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 5 | import com.pt.entity.TorrentMeta; |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 6 | import com.pt.entity.User; |
| 7 | import com.pt.repository.PeerInfoRepository; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 8 | import com.pt.repository.TorrentMetaRepository; |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 9 | import com.pt.repository.UserRepository; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 10 | import com.pt.utils.BencodeCodec; |
| 11 | import org.springframework.beans.factory.annotation.Autowired; |
| 12 | import org.springframework.stereotype.Service; |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 13 | import org.springframework.transaction.annotation.Transactional; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 14 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 15 | import java.io.ByteArrayOutputStream; |
22301102 | ca0fb2f | 2025-06-09 18:40:42 +0800 | [diff] [blame] | 16 | import java.util.*; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 17 | |
| 18 | @Service |
| 19 | public class TrackerService { |
| 20 | |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 21 | @Autowired |
| 22 | private TorrentMetaRepository torrentMetaRepository; |
| 23 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 24 | @Autowired |
| 25 | private PeerInfoRepository peerInfoEntityRepository; |
| 26 | |
| 27 | @Autowired |
| 28 | private TorrentMetaService torrentMetaService; |
| 29 | |
| 30 | @Autowired |
| 31 | private UserRepository userRepository; |
| 32 | |
| 33 | @Transactional |
| 34 | public byte[] handleAnnounce(Map<String, Object> params, String ipAddress, String event) { |
| 35 | |
| 36 | System.out.println("TrackerService------------------------handle announce"); |
| 37 | |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 38 | try { |
22301102 | ca0fb2f | 2025-06-09 18:40:42 +0800 | [diff] [blame] | 39 | if (!params.containsKey("info_hash") || !params.containsKey("peer_id") || !params.containsKey("port")) { |
| 40 | return BencodeCodec.encode(Map.of("failure reason", "Missing required parameters")); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 41 | } |
| 42 | |
22301102 | ca0fb2f | 2025-06-09 18:40:42 +0800 | [diff] [blame] | 43 | System.out.println("Received announce params: " + params); |
| 44 | System.out.println("Client IP: " + ipAddress); |
| 45 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 46 | String username = (String) params.get("passkey"); |
| 47 | User user = null; |
| 48 | if(username != null){ |
| 49 | user = userRepository.findByUsername(username); |
| 50 | if (user == null) { |
| 51 | System.out.println("User not found: " + username); |
| 52 | return BencodeCodec.encode(Map.of("failure reason", "User not found")); |
| 53 | } |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 54 | } |
| 55 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 56 | int compact = params.containsKey("compact") ? (int) params.get("compact") : 1; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 57 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 58 | System.out.println("left" + (long)params.get("left")); |
| 59 | String infoHash = (String)params.get("info_hash"); |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 60 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 61 | switch(event){ |
| 62 | case "started": |
| 63 | String status = (long)params.get("left") == (long)0 ? "seeding" : "downloading"; // 默认状态为下载中 |
| 64 | System.out.println("Event: started"); |
| 65 | Optional<TorrentMeta> meta = torrentMetaRepository.findByInfoHash(infoHash); |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 66 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 67 | if(status.equals("seeding")) { |
| 68 | if(meta.isEmpty()){ |
| 69 | torrentMetaService.save(infoHash, "", 123456789L); |
| 70 | } |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 71 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 72 | String peerId = (String) params.get("peer_id"); |
| 73 | System.out.println("Decoded peer_id: " + peerId); |
| 74 | int port = (int) params.get("port"); |
| 75 | System.out.println("Port: " + port); |
| 76 | |
| 77 | if(peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, (String) params.get("peer_id")).isEmpty()) { |
| 78 | |
| 79 | PeerInfoEntity peer = new PeerInfoEntity(ipAddress, port, peerId); |
| 80 | peer.setInfoHash(infoHash); |
| 81 | peer.setStatus(status); |
| 82 | peer.setIsActive(1); // 默认活跃状态 |
| 83 | peer.setUploaded(0); |
| 84 | peer.setDownloaded(0); |
| 85 | peer.setPeerId((String) params.get("peer_id")); |
| 86 | peerInfoEntityRepository.save(peer); |
| 87 | |
| 88 | } |
| 89 | else { |
| 90 | System.out.println("Peer already exists for info_hash: " + infoHash); |
| 91 | peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, (String) params.get("peer_id")).get(0).setStatus("seeding"); |
| 92 | } |
| 93 | |
| 94 | // 返回成功响应 |
| 95 | Map<String, Object> response = new HashMap<>(); |
| 96 | response.put("interval", 1800); // 客户端应该多久再次请求 |
| 97 | response.put("min interval", 900); // 最小请求间隔 |
| 98 | response.put("complete", peerInfoEntityRepository.findByInfoHash(infoHash).size()); // 种子数 |
| 99 | response.put("incomplete", peerInfoEntityRepository.findDownloadingPeersByInfoHash(infoHash).size()); // 下载者数 |
| 100 | |
| 101 | // 紧凑格式 - 返回当前种子的信息 |
| 102 | if(compact == 1) { |
| 103 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 104 | try { |
| 105 | // 转换IP地址 |
| 106 | String[] ipParts = ipAddress.split("\\."); |
| 107 | for(String part : ipParts) { |
| 108 | baos.write(Integer.parseInt(part)); |
| 109 | } |
| 110 | // 转换端口(大端序) |
| 111 | baos.write((port >> 8) & 0xFF); |
| 112 | baos.write(port & 0xFF); |
| 113 | } catch(Exception e) { |
| 114 | System.out.println("Error encoding peer info: " + e.getMessage()); |
| 115 | } |
| 116 | response.put("peers", baos.toByteArray()); |
| 117 | } else { |
| 118 | // 非紧凑格式 |
| 119 | List<Map<String, Object>> peerList = new ArrayList<>(); |
| 120 | Map<String, Object> peerMap = new HashMap<>(); |
| 121 | peerMap.put("peer id", peerId); |
| 122 | peerMap.put("ip", ipAddress); |
| 123 | peerMap.put("port", port); |
| 124 | peerList.add(peerMap); |
| 125 | response.put("peers", peerList); |
| 126 | } |
| 127 | |
| 128 | return BencodeCodec.encode(response); |
| 129 | |
| 130 | } |
| 131 | |
| 132 | if(status.equals("downloading")) { |
| 133 | System.out.println("Torrent is being downloaded, checking for existing peers..."); |
| 134 | |
| 135 | if(user.getDownloaded() > Constants.DOWNLOAD_EXEMPTION_BYTES){ |
| 136 | if(user.getShareRatio() < Constants.MIN_SHARE_RATIO_THRESHOLD) { |
| 137 | return BencodeCodec.encode(Map.of("failure reason", "Share ratio too low")); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | List<PeerInfoEntity> peers = peerInfoEntityRepository.findSeedingPeersByInfoHash(infoHash); |
| 142 | |
| 143 | for(PeerInfoEntity peer : peers) { |
| 144 | System.out.println("Peer: " + peer.getPeerId() + ", IP: " + peer.getIp() + ", Port: " + peer.getPort()); |
| 145 | } |
| 146 | |
| 147 | String peerId = (String)params.get("peer_id"); |
| 148 | System.out.println("Decoded peer_id: " + peerId); |
| 149 | int port = (int) params.get("port"); |
| 150 | System.out.println("Port: " + port); |
| 151 | if(peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, (String) params.get("peer_id")).isEmpty()) { |
| 152 | PeerInfoEntity peer = new PeerInfoEntity(ipAddress, port, peerId); |
| 153 | peer.setInfoHash(infoHash); |
| 154 | peer.setStatus(status); |
| 155 | peer.setIsActive(1); // 默认活跃状态 |
| 156 | peer.setUploaded(0); |
| 157 | peer.setDownloaded(0); |
| 158 | peer.setPeerId((String) params.get("peer_id")); |
| 159 | peer.setLeft((long)params.get("left")); |
| 160 | peerInfoEntityRepository.save(peer); |
| 161 | } |
| 162 | else { |
| 163 | System.out.println("Peer already exists for info_hash: " + infoHash); |
| 164 | peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, (String) params.get("peer_id")).get(0).setStatus("seeding"); |
| 165 | } |
| 166 | |
| 167 | if (peers.isEmpty()) { |
| 168 | return BencodeCodec.encode(Map.of("failure reason", "Torrent is not being seeded yet")); |
| 169 | } |
| 170 | |
| 171 | System.out.println("solve download, compact = " + compact); |
| 172 | |
| 173 | // 构建正确的响应 |
| 174 | Map<String, Object> response = new HashMap<>(); |
| 175 | // 添加基本信息 |
| 176 | response.put("interval", 1800); |
| 177 | // 客户端应该多久再次请求 |
| 178 | response.put("min interval", 900); |
| 179 | // 最小请求间隔 |
| 180 | response.put("complete", peers.size()); |
| 181 | // 种子数response.put("incomplete", 0); |
| 182 | // 下载者数(这里只返回种子) |
| 183 | // 构建peers列表 |
| 184 | if (compact == 1) { |
| 185 | // 紧凑格式 - 每个peer用6字节表示 (4字节IP + 2字节端口) |
| 186 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 187 | for (PeerInfoEntity tmpPeer : peers) { |
| 188 | try { |
| 189 | // 转换IP地址 |
| 190 | String[] ipParts = tmpPeer.getIp().split("\\."); |
| 191 | for (String part : ipParts) { |
| 192 | baos.write(Integer.parseInt(part)); |
| 193 | } |
| 194 | // 转换端口(大端序) |
| 195 | int tmpPeerPort = tmpPeer.getPort(); |
| 196 | baos.write((tmpPeerPort >> 8) & 0xFF); |
| 197 | baos.write(tmpPeerPort & 0xFF); |
| 198 | } catch (Exception e) { |
| 199 | System.out.println(e.getMessage()); |
| 200 | } |
| 201 | } |
| 202 | response.put("peers", baos.toByteArray()); |
| 203 | System.out.println(response); |
| 204 | } else { |
| 205 | // 非紧凑格式 - 每个peer是一个字典 |
| 206 | List<Map<String, Object>> peerList = new ArrayList<>(); |
| 207 | for (PeerInfoEntity tmpPeer : peers) { |
| 208 | Map<String, Object> peerMap = new HashMap<>(); |
| 209 | peerMap.put("peer id", tmpPeer.getPeerId()); |
| 210 | peerMap.put("ip", tmpPeer.getIp()); |
| 211 | peerMap.put("port", tmpPeer.getPort()); |
| 212 | peerList.add(peerMap); |
| 213 | } |
| 214 | response.put("peers", peerList); |
| 215 | } |
| 216 | System.out.println(BencodeCodec.encode(response)); |
| 217 | |
| 218 | return BencodeCodec.encode(response); |
| 219 | } |
| 220 | break; |
| 221 | case "stopped": |
| 222 | System.out.println("Event: stopped"); |
| 223 | |
| 224 | String peerId = (String) params.get("peer_id"); |
| 225 | PeerInfoEntity peer = peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, peerId).get(0); |
| 226 | peerInfoEntityRepository.delete(peer); // 删除该peer信息 |
| 227 | |
| 228 | if(peerInfoEntityRepository.findByInfoHash(infoHash).isEmpty()) { |
| 229 | torrentMetaRepository.deleteByInfoHash(infoHash); // 如果没有其他peer,删除种子信息 |
| 230 | } |
| 231 | |
| 232 | // 停止事件,通常不需要返回数据 |
| 233 | Map<String, Object> response = new HashMap<>(); |
| 234 | // 添加基本信息 |
| 235 | response.put("interval", 1800); |
| 236 | // 客户端应该多久再次请求 |
| 237 | response.put("min interval", 900); |
| 238 | System.out.println("solve stop"); |
| 239 | |
| 240 | return BencodeCodec.encode(response); |
| 241 | case "completed": |
| 242 | System.out.println("Event: completed"); |
| 243 | |
| 244 | PeerInfoEntity complete_peer = peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, (String) params.get("peer_id")).get(0); |
| 245 | |
| 246 | Map<String, Object> complete_response = new HashMap<>(); |
| 247 | // 添加基本信息 |
| 248 | complete_response.put("interval", 1800); |
| 249 | // 客户端应该多久再次请求 |
| 250 | complete_response.put("min interval", 900); |
| 251 | |
| 252 | complete_response.put("complete", peerInfoEntityRepository.findByInfoHash(infoHash).size()); // 种子数 |
| 253 | complete_response.put("incomplete", peerInfoEntityRepository.findDownloadingPeersByInfoHash(infoHash).size()); // 下载者数 |
| 254 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 255 | for (PeerInfoEntity tmpPeer : peerInfoEntityRepository.findSeedingPeersByInfoHash(infoHash)) { |
| 256 | try { |
| 257 | // 转换IP地址 |
| 258 | String[] ipParts = tmpPeer.getIp().split("\\."); |
| 259 | for (String part : ipParts) { |
| 260 | baos.write(Integer.parseInt(part)); |
| 261 | } |
| 262 | // 转换端口(大端序) |
| 263 | int tmpPeerPort = tmpPeer.getPort(); |
| 264 | baos.write((tmpPeerPort >> 8) & 0xFF); |
| 265 | baos.write(tmpPeerPort & 0xFF); |
| 266 | } catch (Exception e) { |
| 267 | System.out.println(e.getMessage()); |
| 268 | } |
| 269 | } |
| 270 | complete_response.put("peers", baos.toByteArray()); |
| 271 | System.out.println("solve complete"); |
| 272 | if(complete_peer.getStatus().equals("downloading")) { |
| 273 | user.setDownloaded(user.getDownloaded() + (long)params.get("downloaded")); |
| 274 | complete_peer.setStatus("seeding"); |
| 275 | peerInfoEntityRepository.save(complete_peer); |
| 276 | } |
| 277 | if(complete_peer.getStatus().equals("seeding")) { |
| 278 | user.setUploaded(user.getUploaded() + (long)params.get("uploaded")); |
| 279 | } |
| 280 | |
| 281 | user.setShareRatio(user.getUploaded() / (double) Math.max(user.getDownloaded(), 1)); |
| 282 | |
| 283 | userRepository.save(user); // 保存用户信息 |
| 284 | |
| 285 | return BencodeCodec.encode(complete_response); |
| 286 | default: |
| 287 | System.out.println(event); |
| 288 | System.out.println("Event: unknown or not specified"); |
| 289 | |
| 290 | PeerInfoEntity other_peer = peerInfoEntityRepository.findByInfoHashAndPeerId(infoHash, (String) params.get("peer_id")).get(0); |
| 291 | |
| 292 | Map<String, Object> other_response = new HashMap<>(); |
| 293 | |
| 294 | // 添加基本信息 |
| 295 | other_response.put("interval", 1800); |
| 296 | // 客户端应该多久再次请求 |
| 297 | other_response.put("min interval", 900); |
| 298 | |
| 299 | other_response.put("complete", peerInfoEntityRepository.findByInfoHash(infoHash).size()); // 种子数 |
| 300 | other_response.put("incomplete", peerInfoEntityRepository.findDownloadingPeersByInfoHash(infoHash).size()); // 下载者数 |
| 301 | |
| 302 | ByteArrayOutputStream other_baos = new ByteArrayOutputStream(); |
| 303 | for (PeerInfoEntity tmpPeer : peerInfoEntityRepository.findSeedingPeersByInfoHash(infoHash)) { |
| 304 | try { |
| 305 | // 转换IP地址 |
| 306 | String[] ipParts = tmpPeer.getIp().split("\\."); |
| 307 | for (String part : ipParts) { |
| 308 | other_baos.write(Integer.parseInt(part)); |
| 309 | } |
| 310 | // 转换端口(大端序) |
| 311 | int tmpPeerPort = tmpPeer.getPort(); |
| 312 | other_baos.write((tmpPeerPort >> 8) & 0xFF); |
| 313 | other_baos.write(tmpPeerPort & 0xFF); |
| 314 | } catch (Exception e) { |
| 315 | System.out.println(e.getMessage()); |
| 316 | } |
| 317 | } |
| 318 | other_response.put("peers", other_baos.toByteArray()); |
| 319 | if(other_peer.getStatus().equals("downloading")) { |
| 320 | user.setDownloaded(user.getDownloaded() + (long)params.get("downloaded")); |
| 321 | } |
| 322 | if(other_peer.getStatus().equals("seeding")) { |
| 323 | user.setUploaded(user.getUploaded() + (long)params.get("uploaded")); |
| 324 | } |
| 325 | |
| 326 | user.setShareRatio(user.getUploaded() / (double) Math.max(user.getDownloaded(), 1)); |
| 327 | |
| 328 | userRepository.save(user); // 保存用户信息 |
| 329 | return BencodeCodec.encode(other_response); |
22301102 | ca0fb2f | 2025-06-09 18:40:42 +0800 | [diff] [blame] | 330 | } |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 331 | |
Edwardsamaxl | cba512d | 2025-06-09 21:17:29 +0800 | [diff] [blame^] | 332 | return BencodeCodec.encode(Map.of("failure reason", "Event not handled")); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 333 | } catch (Exception e) { |
22301102 | ca0fb2f | 2025-06-09 18:40:42 +0800 | [diff] [blame] | 334 | e.printStackTrace(); // 打印异常堆栈,方便定位错误 |
| 335 | return BencodeCodec.encode(Map.of("failure reason", "Internal server error")); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 336 | } |
| 337 | } |
| 338 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 339 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 340 | |
22301102 | ca0fb2f | 2025-06-09 18:40:42 +0800 | [diff] [blame] | 341 | } |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 342 | |