Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 1 | package com.pt.service; |
| 2 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 3 | import com.pt.entity.PeerInfoEntity; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 4 | import com.pt.entity.TorrentMeta; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 5 | import com.pt.exception.ResourceNotFoundException; |
| 6 | import com.pt.repository.PeerInfoRepository; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 7 | import com.pt.repository.TorrentMetaRepository; |
| 8 | import com.pt.utils.BencodeCodec; |
| 9 | import org.springframework.beans.factory.annotation.Autowired; |
| 10 | import org.springframework.stereotype.Service; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 11 | import org.springframework.transaction.annotation.Transactional; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 12 | |
| 13 | import java.nio.charset.StandardCharsets; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 14 | import java.time.LocalDateTime; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 15 | import java.util.List; |
| 16 | import java.util.Map; |
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 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 24 | @Autowired |
| 25 | private PeerInfoRepository peerInfoRepository; |
| 26 | |
| 27 | @Autowired |
| 28 | private TorrentStatsService statsService; |
| 29 | |
| 30 | @Transactional |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 31 | public byte[] handleAnnounce(Map<String, String[]> params, String ipAddress) { |
| 32 | try { |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 33 | // 验证必要参数 |
| 34 | if (!params.containsKey("info_hash") || !params.containsKey("peer_id") |
| 35 | || !params.containsKey("port")) { |
| 36 | return errorResponse("Missing required parameters"); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 37 | } |
| 38 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 39 | // 解析参数 |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 40 | String infoHash = decodeParam(params.get("info_hash")[0]); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 41 | String peerId = decodeParam(params.get("peer_id")[0]); |
| 42 | int port = Integer.parseInt(params.get("port")[0]); |
| 43 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 44 | // 获取事件类型 |
| 45 | String event = getEventParam(params); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 46 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 47 | // 获取流量数据 |
| 48 | long uploaded = getLongParam(params, "uploaded", 0); |
| 49 | long downloaded = getLongParam(params, "downloaded", 0); |
| 50 | long left = getLongParam(params, "left", 0); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 51 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 52 | // 验证种子是否存在 |
| 53 | TorrentMeta meta = torrentMetaRepository.findByInfoHash(infoHash); |
| 54 | if (meta == null) { |
| 55 | return errorResponse("Torrent not found: " + infoHash); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 56 | } |
| 57 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 58 | // 创建或更新 peer 信息 |
| 59 | PeerInfoEntity peer = findOrCreatePeer(peerId, infoHash); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 60 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 61 | // 设置 peer 属性 |
| 62 | setPeerProperties(peer, ipAddress, port, uploaded, downloaded, left); |
| 63 | |
| 64 | // 处理事件类型 |
| 65 | handlePeerEvent(event, peer, left); |
| 66 | |
| 67 | // 保存 peer |
| 68 | peerInfoRepository.save(peer); |
| 69 | |
| 70 | // 更新种子统计信息 |
| 71 | statsService.updateTorrentStats(infoHash); |
| 72 | |
| 73 | // 获取 peer 列表响应 |
| 74 | List<PeerInfoEntity> activePeers = peerInfoRepository.findActivePeersByInfoHash(infoHash); |
| 75 | byte[] peerBytes = buildPeerResponse(activePeers); |
| 76 | |
| 77 | // 返回成功响应 |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 78 | return BencodeCodec.buildTrackerResponse(1800, peerBytes); |
| 79 | } catch (Exception e) { |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 80 | return errorResponse("Internal server error"); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 81 | } |
| 82 | } |
| 83 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 84 | // 辅助方法:获取事件参数 |
| 85 | private String getEventParam(Map<String, String[]> params) { |
| 86 | return params.containsKey("event") ? |
| 87 | decodeParam(params.get("event")[0]) : "update"; |
| 88 | } |
| 89 | |
| 90 | // 辅助方法:获取长整型参数 |
| 91 | private long getLongParam(Map<String, String[]> params, String key, long defaultValue) { |
| 92 | return params.containsKey(key) ? |
| 93 | Long.parseLong(params.get(key)[0]) : defaultValue; |
| 94 | } |
| 95 | |
| 96 | // 辅助方法:查找或创建 peer |
| 97 | private PeerInfoEntity findOrCreatePeer(String peerId, String infoHash) { |
| 98 | return peerInfoRepository.findByPeerIdAndInfoHash(peerId, infoHash) |
| 99 | .orElseGet(PeerInfoEntity::new); |
| 100 | } |
| 101 | |
| 102 | // 辅助方法:设置 peer 属性 |
| 103 | private void setPeerProperties(PeerInfoEntity peer, String ip, int port, |
| 104 | long uploaded, long downloaded, long left) { |
| 105 | peer.setIp(ip); |
| 106 | peer.setPort(port); |
| 107 | peer.setPeerId(peer.getPeerId() != null ? peer.getPeerId() : ""); // 防止 NPE |
| 108 | peer.setInfoHash(peer.getInfoHash() != null ? peer.getInfoHash() : ""); |
| 109 | peer.setUploaded(uploaded); |
| 110 | peer.setDownloaded(downloaded); |
| 111 | peer.setLeft(left); |
| 112 | peer.setLastSeen(LocalDateTime.now()); |
| 113 | } |
| 114 | |
| 115 | // 辅助方法:处理 peer 事件 |
| 116 | private void handlePeerEvent(String event, PeerInfoEntity peer, long left) { |
| 117 | switch (event) { |
| 118 | case "started": |
| 119 | peer.setStatus("downloading"); |
| 120 | peer.setActive(true); |
| 121 | break; |
| 122 | |
| 123 | case "stopped": |
| 124 | peer.setActive(false); |
| 125 | break; |
| 126 | |
| 127 | case "completed": |
| 128 | handleCompletedEvent(peer); |
| 129 | break; |
| 130 | |
| 131 | case "update": |
| 132 | default: |
| 133 | handleUpdateEvent(peer, left); |
| 134 | break; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // 处理完成事件 |
| 139 | private void handleCompletedEvent(PeerInfoEntity peer) { |
| 140 | peer.setStatus("completed"); |
| 141 | peer.setActive(true); |
| 142 | peer.setLeft(0); |
| 143 | incrementCompletedCount(peer.getInfoHash()); |
| 144 | } |
| 145 | |
| 146 | // 处理更新事件 |
| 147 | private void handleUpdateEvent(PeerInfoEntity peer, long left) { |
| 148 | if (left == 0 && "downloading".equals(peer.getStatus())) { |
| 149 | // 检测到下载完成 |
| 150 | peer.setStatus("completed"); |
| 151 | incrementCompletedCount(peer.getInfoHash()); |
| 152 | } |
| 153 | peer.setActive(true); |
| 154 | } |
| 155 | |
| 156 | // 增加完成次数 |
| 157 | private void incrementCompletedCount(String infoHash) { |
| 158 | TorrentMeta meta = torrentMetaRepository.findByInfoHash(infoHash); |
| 159 | if (meta != null) { |
| 160 | statsService.incrementCompletedCount(meta.getId()); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | // 构建 peer 响应 |
| 165 | private byte[] buildPeerResponse(List<PeerInfoEntity> peers) { |
| 166 | List<String> ips = peers.stream().map(PeerInfoEntity::getIp).toList(); |
| 167 | List<Integer> ports = peers.stream().map(PeerInfoEntity::getPort).toList(); |
| 168 | return BencodeCodec.buildCompactPeers(ips, ports); |
| 169 | } |
| 170 | |
| 171 | // 构建错误响应 |
| 172 | private byte[] errorResponse(String reason) { |
| 173 | return BencodeCodec.encode(Map.of("failure reason", reason)); |
| 174 | } |
| 175 | |
| 176 | // 解码参数 |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 177 | private String decodeParam(String raw) { |
| 178 | return new String(raw.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); |
| 179 | } |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 180 | } |