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; |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 5 | import com.pt.entity.User; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 6 | import com.pt.exception.ResourceNotFoundException; |
| 7 | import com.pt.repository.PeerInfoRepository; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 8 | import com.pt.repository.TorrentMetaRepository; |
| 9 | import com.pt.utils.BencodeCodec; |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 10 | import org.slf4j.Logger; |
| 11 | import org.slf4j.LoggerFactory; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 12 | import org.springframework.beans.factory.annotation.Autowired; |
| 13 | import org.springframework.stereotype.Service; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 14 | import org.springframework.transaction.annotation.Transactional; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 15 | |
| 16 | import java.nio.charset.StandardCharsets; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 17 | import java.time.LocalDateTime; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 18 | import java.util.List; |
| 19 | import java.util.Map; |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 20 | |
| 21 | @Service |
| 22 | public class TrackerService { |
| 23 | |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 24 | private static final Logger logger = LoggerFactory.getLogger(TrackerService.class); |
| 25 | |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 26 | @Autowired |
| 27 | private TorrentMetaRepository torrentMetaRepository; |
| 28 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 29 | @Autowired |
| 30 | private PeerInfoRepository peerInfoRepository; |
| 31 | |
| 32 | @Autowired |
| 33 | private TorrentStatsService statsService; |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 34 | |
| 35 | @Autowired |
| 36 | private UserService userService; |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 37 | |
| 38 | @Transactional |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 39 | public byte[] handleAnnounce(Map<String, String[]> params, String ipAddress) { |
| 40 | try { |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 41 | // 验证必要参数 |
| 42 | if (!params.containsKey("info_hash") || !params.containsKey("peer_id") |
| 43 | || !params.containsKey("port")) { |
| 44 | return errorResponse("Missing required parameters"); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 45 | } |
| 46 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 47 | // 解析参数 |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 48 | String infoHash = decodeParam(params.get("info_hash")[0]); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 49 | String peerId = decodeParam(params.get("peer_id")[0]); |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 50 | String username = decodeParam(params.get("username")[0]); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 51 | int port = Integer.parseInt(params.get("port")[0]); |
| 52 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 53 | // 获取事件类型 |
| 54 | String event = getEventParam(params); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 55 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 56 | // 获取流量数据 |
| 57 | long uploaded = getLongParam(params, "uploaded", 0); |
| 58 | long downloaded = getLongParam(params, "downloaded", 0); |
| 59 | long left = getLongParam(params, "left", 0); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 60 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 61 | // 验证种子是否存在 |
| 62 | TorrentMeta meta = torrentMetaRepository.findByInfoHash(infoHash); |
| 63 | if (meta == null) { |
| 64 | return errorResponse("Torrent not found: " + infoHash); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 65 | } |
| 66 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 67 | // 创建或更新 peer 信息 |
| 68 | PeerInfoEntity peer = findOrCreatePeer(peerId, infoHash); |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 69 | |
| 70 | // 计算流量增量 |
| 71 | long uploadedDelta = uploaded - peer.getUploaded(); |
| 72 | long downloadedDelta = downloaded - peer.getDownloaded(); |
| 73 | |
| 74 | // 更新用户总流量 |
| 75 | updateUserTraffic(username, uploadedDelta, downloadedDelta); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 76 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 77 | // 设置 peer 属性 |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 78 | peer.setUsername(username); |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 79 | setPeerProperties(peer, ipAddress, port, uploaded, downloaded, left); |
| 80 | |
| 81 | // 处理事件类型 |
| 82 | handlePeerEvent(event, peer, left); |
| 83 | |
| 84 | // 保存 peer |
| 85 | peerInfoRepository.save(peer); |
| 86 | |
| 87 | // 更新种子统计信息 |
| 88 | statsService.updateTorrentStats(infoHash); |
| 89 | |
| 90 | // 获取 peer 列表响应 |
| 91 | List<PeerInfoEntity> activePeers = peerInfoRepository.findActivePeersByInfoHash(infoHash); |
| 92 | byte[] peerBytes = buildPeerResponse(activePeers); |
| 93 | |
| 94 | // 返回成功响应 |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 95 | return BencodeCodec.buildTrackerResponse(1800, peerBytes); |
| 96 | } catch (Exception e) { |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 97 | return errorResponse("Internal server error"); |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 98 | } |
| 99 | } |
| 100 | |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 101 | // 添加更新用户流量的方法 |
| 102 | @Transactional |
| 103 | private void updateUserTraffic(String username, long uploadedDelta, long downloadedDelta) { |
| 104 | if (uploadedDelta <= 0 && downloadedDelta <= 0) { |
| 105 | return; // 没有新的流量,不需要更新 |
| 106 | } |
| 107 | |
| 108 | User user = userService.findByUsername(username); |
| 109 | if (user != null) { |
| 110 | long oldUploaded = user.getUploaded(); |
| 111 | long oldDownloaded = user.getDownloaded(); |
| 112 | |
| 113 | user.setUploaded(oldUploaded + uploadedDelta); |
| 114 | user.setDownloaded(oldDownloaded + downloadedDelta); |
| 115 | userService.save(user); |
| 116 | |
| 117 | // 更新用户等级 |
| 118 | userService.updateUserLevel(user.getUid()); |
| 119 | |
| 120 | logger.info("用户 {} 流量更新: 上传 {} -> {} (+{}), 下载 {} -> {} (+{})", |
| 121 | username, |
| 122 | oldUploaded, user.getUploaded(), uploadedDelta, |
| 123 | oldDownloaded, user.getDownloaded(), downloadedDelta); |
| 124 | } else { |
| 125 | logger.warn("尝试更新不存在的用户流量: {}", username); |
| 126 | } |
| 127 | } |
| 128 | |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 129 | // 辅助方法:获取事件参数 |
| 130 | private String getEventParam(Map<String, String[]> params) { |
| 131 | return params.containsKey("event") ? |
| 132 | decodeParam(params.get("event")[0]) : "update"; |
| 133 | } |
| 134 | |
| 135 | // 辅助方法:获取长整型参数 |
| 136 | private long getLongParam(Map<String, String[]> params, String key, long defaultValue) { |
| 137 | return params.containsKey(key) ? |
| 138 | Long.parseLong(params.get(key)[0]) : defaultValue; |
| 139 | } |
| 140 | |
| 141 | // 辅助方法:查找或创建 peer |
| 142 | private PeerInfoEntity findOrCreatePeer(String peerId, String infoHash) { |
| 143 | return peerInfoRepository.findByPeerIdAndInfoHash(peerId, infoHash) |
22301102 | f567030 | 2025-06-08 14:10:02 +0800 | [diff] [blame] | 144 | .orElseGet(() -> { |
| 145 | PeerInfoEntity newPeer = new PeerInfoEntity(); |
| 146 | newPeer.setPeerId(peerId); |
| 147 | newPeer.setInfoHash(infoHash); |
| 148 | newPeer.setActive(true); |
| 149 | newPeer.setStatus("downloading"); |
| 150 | newPeer.setLastSeen(LocalDateTime.now()); |
| 151 | return newPeer; |
| 152 | }); |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 153 | } |
| 154 | |
| 155 | // 辅助方法:设置 peer 属性 |
| 156 | private void setPeerProperties(PeerInfoEntity peer, String ip, int port, |
| 157 | long uploaded, long downloaded, long left) { |
| 158 | peer.setIp(ip); |
| 159 | peer.setPort(port); |
| 160 | peer.setPeerId(peer.getPeerId() != null ? peer.getPeerId() : ""); // 防止 NPE |
| 161 | peer.setInfoHash(peer.getInfoHash() != null ? peer.getInfoHash() : ""); |
| 162 | peer.setUploaded(uploaded); |
| 163 | peer.setDownloaded(downloaded); |
| 164 | peer.setLeft(left); |
| 165 | peer.setLastSeen(LocalDateTime.now()); |
| 166 | } |
| 167 | |
| 168 | // 辅助方法:处理 peer 事件 |
| 169 | private void handlePeerEvent(String event, PeerInfoEntity peer, long left) { |
| 170 | switch (event) { |
| 171 | case "started": |
| 172 | peer.setStatus("downloading"); |
| 173 | peer.setActive(true); |
| 174 | break; |
| 175 | |
| 176 | case "stopped": |
| 177 | peer.setActive(false); |
| 178 | break; |
| 179 | |
| 180 | case "completed": |
| 181 | handleCompletedEvent(peer); |
| 182 | break; |
| 183 | |
| 184 | case "update": |
| 185 | default: |
| 186 | handleUpdateEvent(peer, left); |
| 187 | break; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | // 处理完成事件 |
| 192 | private void handleCompletedEvent(PeerInfoEntity peer) { |
| 193 | peer.setStatus("completed"); |
| 194 | peer.setActive(true); |
| 195 | peer.setLeft(0); |
| 196 | incrementCompletedCount(peer.getInfoHash()); |
| 197 | } |
| 198 | |
| 199 | // 处理更新事件 |
| 200 | private void handleUpdateEvent(PeerInfoEntity peer, long left) { |
| 201 | if (left == 0 && "downloading".equals(peer.getStatus())) { |
| 202 | // 检测到下载完成 |
| 203 | peer.setStatus("completed"); |
| 204 | incrementCompletedCount(peer.getInfoHash()); |
| 205 | } |
| 206 | peer.setActive(true); |
| 207 | } |
| 208 | |
| 209 | // 增加完成次数 |
| 210 | private void incrementCompletedCount(String infoHash) { |
| 211 | TorrentMeta meta = torrentMetaRepository.findByInfoHash(infoHash); |
| 212 | if (meta != null) { |
| 213 | statsService.incrementCompletedCount(meta.getId()); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // 构建 peer 响应 |
| 218 | private byte[] buildPeerResponse(List<PeerInfoEntity> peers) { |
| 219 | List<String> ips = peers.stream().map(PeerInfoEntity::getIp).toList(); |
| 220 | List<Integer> ports = peers.stream().map(PeerInfoEntity::getPort).toList(); |
| 221 | return BencodeCodec.buildCompactPeers(ips, ports); |
| 222 | } |
| 223 | |
| 224 | // 构建错误响应 |
| 225 | private byte[] errorResponse(String reason) { |
| 226 | return BencodeCodec.encode(Map.of("failure reason", reason)); |
| 227 | } |
| 228 | |
| 229 | // 解码参数 |
Edwardsamaxl | f1bf7ad | 2025-06-03 23:52:16 +0800 | [diff] [blame] | 230 | private String decodeParam(String raw) { |
| 231 | return new String(raw.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); |
| 232 | } |
ystx | dfd42b3 | 2025-06-07 13:26:52 +0800 | [diff] [blame] | 233 | } |