完成部分资源功能
Change-Id: Idfaef80363ef191a294e52ae53cdd4e2b3e7ccef
diff --git a/src/main/java/com/pt/controller/ResourceController.java b/src/main/java/com/pt/controller/ResourceController.java
index caf2050..8e08e16 100644
--- a/src/main/java/com/pt/controller/ResourceController.java
+++ b/src/main/java/com/pt/controller/ResourceController.java
@@ -1,10 +1,8 @@
package com.pt.controller;
-import com.pt.Item.ResourceInfo;
import com.pt.constant.Constants;
import com.pt.entity.Resource;
import com.pt.entity.User;
-import com.pt.service.DownloadService;
import com.pt.service.ResourceService;
import com.pt.service.UserService;
import com.pt.utils.JWTUtils;
@@ -12,10 +10,11 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
-import java.util.ArrayList;
+import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.io.File;
@RestController
@RequestMapping("/api/resource")
@@ -25,7 +24,6 @@
@Autowired
private ResourceService resourceService;
private UserService userService;
- private DownloadService downloadService;
@GetMapping("/list/all")
public ResponseEntity<?> getAllResources(@RequestHeader("token") String token,
@@ -41,27 +39,7 @@
if (resources.isEmpty()) {
return ResponseEntity.noContent().build();
}
-
- List<ResourceInfo> resourceInfos = new ArrayList<>();
- for (Resource resource : resources) {
- ResourceInfo resourceInfo = new ResourceInfo();
- resourceInfo.setResourceId(resource.getResourceId());
- resourceInfo.setName(resource.getName());
- resourceInfo.setSize(resource.getSize());
- resourceInfo.setDescription(resource.getDescription());
- resourceInfo.setAuthor(resource.getAuthor());
- resourceInfo.setPublishTime(resource.getPublishTime());
-
- int downloadCount = downloadService.searchByResourceId(resource.getResourceId()).size();
- int seedCount = 0; // TODO: 需要根据tracker信息计算该资源的种子树
- resourceInfo.setDownloadCount(downloadCount);
- resourceInfo.setSeedCount(seedCount);
- resourceInfos.add(resourceInfo);
- }
-
- ans.put("message", "Resources retrieved successfully");
- ans.put("resources", resourceInfos);
- return ResponseEntity.ok(ans);
+ return ResponseEntity.ok(resources);
}
@GetMapping("/list/user")
@@ -89,29 +67,30 @@
@RequestParam("description") String description) {
Map<String, Object> ans = new HashMap<>();
- if(!JWTUtils.checkToken(token, username, Constants.UserRole.USER)){
+ if (!JWTUtils.checkToken(token, username, Constants.UserRole.USER)) {
ans.put("result", "Invalid token");
return ResponseEntity.badRequest().body(ans);
}
User user = userService.findByUsername(username);
- if(user.getLevel() < 2){
+ if (user == null || user.getLevel() < 2) {
ans.put("result", "Insufficient permissions to publish resources");
return ResponseEntity.status(403).body(ans);
}
- /*
- * TODO: 在这里实现资源发布的逻辑
- * 需要将资源同步到Tracker服务器
- * 种子文件需要保存
- */
-
- resourceService.publishResource(name, description, username, size);
+ try {
+ resourceService.publishResource(name, description, username, size);
+ } catch (Exception e) {
+ ans.put("result", "Failed to publish resource: " + e.getMessage());
+ return ResponseEntity.status(500).body(ans);
+ }
ans.put("result", "Resource published successfully");
return ResponseEntity.ok(ans);
}
+
+
@GetMapping("/get/{resourceId}")
public ResponseEntity<?> getResourceById(@PathVariable("resourceId") int resourceId,
@RequestHeader("token") String token,
@@ -133,7 +112,7 @@
@GetMapping("/download/{resourceId}")
public ResponseEntity<?> downloadResource(@PathVariable("resourceId") int resourceId,
@RequestHeader("token") String token,
- @RequestParam("username") String username) {
+ @RequestParam("username") String username) throws IOException {
Map<String, Object> ans = new HashMap<>();
if(!JWTUtils.checkToken(token, username, Constants.UserRole.USER)) {
@@ -141,13 +120,16 @@
return ResponseEntity.badRequest().body(ans);
}
- /*
- * TODO: 在这里实现下载资源的方法
- * 从本地的种子文件目录获取种子文件
- */
+ Resource resource = resourceService.getResourceById(resourceId);
+ byte[] file = resourceService.getTorrentFileByResource(resource, username);
+ if (file == null) {
+ return ResponseEntity.notFound().build();
+ }
- // Here you would typically return the file or a download link
- return ResponseEntity.ok(ans);
+ return ResponseEntity.ok()
+ .header("Content-Type", "application/x-bittorrent")
+ .header("Content-Disposition", "attachment; filename=\"" + resource.getName() + ".torrent\"")
+ .body(file);
}
@GetMapping("/search")
@@ -176,19 +158,19 @@
Resource resource = resourceService.getResourceById(resourceId);
if(!JWTUtils.checkToken(token, username, Constants.UserRole.ADMIN) || resource == null || !resource.getAuthor().equals(username)) {
- ans.put("result", "Invalid token");
+ ans.put("result", "Invalid token or insufficient permissions");
return ResponseEntity.badRequest().body(ans);
}
- /*
- * TODO: 在这里实现删除资源的方法
- * 需要同步到Tracker服务器
- */
-
-
- resourceService.deleteResource(resourceId);
+ try {
+ resourceService.deleteResource(resourceId);
+ } catch (Exception e) {
+ ans.put("result", "Failed to delete resource: " + e.getMessage());
+ return ResponseEntity.status(500).body(ans);
+ }
ans.put("result", "Resource deleted successfully");
return ResponseEntity.ok(ans);
}
+
}
diff --git a/src/main/java/com/pt/entity/Download.java b/src/main/java/com/pt/entity/Download.java
index 1ae9354..38b955a 100644
--- a/src/main/java/com/pt/entity/Download.java
+++ b/src/main/java/com/pt/entity/Download.java
@@ -14,14 +14,14 @@
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int downloadId;
- private int resourceId;
+ private String resourceId;
private String downloader;
private LocalDateTime downloadTime;
public Download() {
}
- public Download(int downloadId, int resourceId, String downloader, LocalDateTime downloadTime) {
+ public Download(int downloadId, String resourceId, String downloader, LocalDateTime downloadTime) {
this.downloadId = downloadId;
this.resourceId = resourceId;
this.downloader = downloader;
@@ -34,10 +34,10 @@
public void setDownloadId(int downloadId) {
this.downloadId = downloadId;
}
- public int getResourceId() {
+ public String getResourceId() {
return resourceId;
}
- public void setResourceId(int resourceId) {
+ public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
public String getDownloader() {
diff --git a/src/main/java/com/pt/entity/Resource.java b/src/main/java/com/pt/entity/Resource.java
index 2d8567b..9b7da39 100644
--- a/src/main/java/com/pt/entity/Resource.java
+++ b/src/main/java/com/pt/entity/Resource.java
@@ -21,6 +21,7 @@
private String author;
private String description;
+ private byte[] torrentData;
public Resource() {
}
@@ -69,6 +70,13 @@
public void setDescription(String description) {
this.description = description;
}
+ public byte[] getTorrentData() {
+ return torrentData;
+ }
+
+ public void setTorrentData(byte[] torrentData) {
+ this.torrentData = torrentData;
+ }
/*
* 重写toString方法,将资源信息以JSON字符串形式返回
diff --git a/src/main/java/com/pt/entity/TorrentMeta.java b/src/main/java/com/pt/entity/TorrentMeta.java
new file mode 100644
index 0000000..3823181
--- /dev/null
+++ b/src/main/java/com/pt/entity/TorrentMeta.java
@@ -0,0 +1,60 @@
+package com.pt.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+import java.time.LocalDateTime;
+
+@Entity
+public class TorrentMeta {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String filename;
+ private String infoHash;
+ private Long size;
+ private LocalDateTime uploadTime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ public String getInfoHash() {
+ return infoHash;
+ }
+
+ public void setInfoHash(String infoHash) {
+ this.infoHash = infoHash;
+ }
+
+ public Long getSize() {
+ return size;
+ }
+
+ public void setSize(Long size) {
+ this.size = size;
+ }
+
+ public LocalDateTime getUploadTime() {
+ return uploadTime;
+ }
+
+ public void setUploadTime(LocalDateTime uploadTime) {
+ this.uploadTime = uploadTime;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt/repository/DownloadRepository.java b/src/main/java/com/pt/repository/DownloadRepository.java
index 360a1c0..741f142 100644
--- a/src/main/java/com/pt/repository/DownloadRepository.java
+++ b/src/main/java/com/pt/repository/DownloadRepository.java
@@ -12,7 +12,7 @@
* @param resourceId the ID of the resource
* @return a list of downloads associated with the specified resource
*/
- List<Download> findByResourceId(int resourceId);
+ List<Download> findByResourceId(String resourceId);
/**
* Finds downloads by the downloader's username.
diff --git a/src/main/java/com/pt/repository/TorrentMetaRepository.java b/src/main/java/com/pt/repository/TorrentMetaRepository.java
new file mode 100644
index 0000000..839fc01
--- /dev/null
+++ b/src/main/java/com/pt/repository/TorrentMetaRepository.java
@@ -0,0 +1,10 @@
+package com.pt.repository;
+
+import com.pt.entity.TorrentMeta;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TorrentMetaRepository extends JpaRepository<TorrentMeta, Long> {
+ TorrentMeta findByInfoHash(String infoHash);
+
+ TorrentMeta findByFilename(String filename);
+}
diff --git a/src/main/java/com/pt/service/DownloadService.java b/src/main/java/com/pt/service/DownloadService.java
index c02aea1..43b80e6 100644
--- a/src/main/java/com/pt/service/DownloadService.java
+++ b/src/main/java/com/pt/service/DownloadService.java
@@ -13,14 +13,12 @@
@Autowired
private DownloadRepository downloadRepository;
- public DownloadService(DownloadRepository downloadRepository) {
- this.downloadRepository = downloadRepository;
+ public void save(Download download) {
+ downloadRepository.save(download);
}
- public List<Download> searchByResourceId(int resourceId) {
+ public List<Download> getByResourceId(String resourceId) {
return downloadRepository.findByResourceId(resourceId);
}
- /*
- TODO: 添加下载需要的服务;
- */
+
}
diff --git a/src/main/java/com/pt/service/ResourceService.java b/src/main/java/com/pt/service/ResourceService.java
index a91bc7d..1d0e797 100644
--- a/src/main/java/com/pt/service/ResourceService.java
+++ b/src/main/java/com/pt/service/ResourceService.java
@@ -1,12 +1,21 @@
package com.pt.service;
+import com.pt.entity.Download;
import com.pt.entity.Resource;
+import com.pt.entity.TorrentMeta;
+import com.pt.repository.DownloadRepository;
import com.pt.repository.ResourceRepository;
+import com.pt.repository.TorrentMetaRepository;
+import com.pt.utils.BencodeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.List;
+import java.util.Map;
@Service
public class ResourceService {
@@ -14,37 +23,74 @@
@Autowired
private ResourceRepository resourceRepository;
- public ResourceService(ResourceRepository resourceRepository) {
- this.resourceRepository = resourceRepository;
- }
+ @Autowired
+ private DownloadRepository downloadRepository;
+
+ @Autowired
+ private TorrentService torrentService;
+
+ @Autowired
+ private TorrentMetaRepository torrentMetaRepository;
public List<Resource> getAllResources() {
return resourceRepository.findAll();
}
- public void publishResource(String name, String description, String publisher, double size) {
+ public List<Resource> getResourcesByAuthor(String username) {
+ return resourceRepository.findByAuthor(username);
+ }
+
+ // 发布资源时,将种子文件二进制数据保存到数据库
+ public void publishResource(String name, String description, String author, double size) throws Exception {
+ // 保存资源信息到数据库,包括种子文件的二进制数据
Resource resource = new Resource();
resource.setName(name);
- resource.setSize(size);
resource.setDescription(description);
- resource.setAuthor(publisher);
+ resource.setAuthor(author);
+ resource.setSize(size);
resource.setPublishTime(LocalDateTime.now());
+
+ // 生成种子数据 byte[]
+ byte[] torrentData = torrentService.generateTorrentBytes("uploads/" + name);
+ resource.setTorrentData(torrentData);
+
resourceRepository.save(resource);
}
- public Resource getResourceById(int resourceId) {
- return resourceRepository.findById(resourceId).orElse(null);
+ // 获取资源时,返回BLOB字段内容作为torrent文件
+ public byte[] getTorrentFileByResource(Resource resource, String username) {
+ if(resource == null || resource.getTorrentData() == null) return null;
+
+ // 记录下载日志
+ Download download = new Download();
+ download.setResourceId(String.valueOf(resource.getResourceId()));
+ download.setDownloader(username);
+ download.setDownloadTime(LocalDateTime.now());
+ downloadRepository.save(download);
+
+ return resource.getTorrentData();
}
+ public Resource getResourceById(int id) {
+ return resourceRepository.findById(id).orElse(null);
+ }
+
+ public void deleteResource(int id) {
+ Resource resource = getResourceById(id);
+ if (resource != null) {
+ // 删除数据库资源记录
+ resourceRepository.deleteById(id);
+
+ // 删除对应的 TorrentMeta 元信息
+ TorrentMeta meta = torrentMetaRepository.findByFilename(resource.getName());
+ if (meta != null) {
+ torrentMetaRepository.delete(meta);
+ }
+
+ }
+ }
public List<Resource> searchByQuery(String query) {
return resourceRepository.findByNameContainingIgnoreCase(query);
}
- public List<Resource> getResourcesByAuthor(String author) {
- return resourceRepository.findByAuthor(author);
- }
-
- public void deleteResource(int resourceId) {
- resourceRepository.deleteById(resourceId);
- }
}
diff --git a/src/main/java/com/pt/service/TorrentService.java b/src/main/java/com/pt/service/TorrentService.java
new file mode 100644
index 0000000..dcbee69
--- /dev/null
+++ b/src/main/java/com/pt/service/TorrentService.java
@@ -0,0 +1,83 @@
+package com.pt.service;
+
+import com.pt.entity.TorrentMeta;
+import com.pt.repository.TorrentMetaRepository;
+import com.pt.utils.BencodeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.security.MessageDigest;
+import java.time.LocalDateTime;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Service
+public class TorrentService {
+
+ // Tracker 服务器的 announce 地址
+ private static final String ANNOUNCE_URL = "http://localhost:8080/announce";
+ private static final int PIECE_LENGTH = 256 * 1024;
+
+ @Autowired
+ private TorrentMetaRepository torrentMetaRepository;
+
+ public byte[] generateTorrentBytes(String sourceFilePath) throws Exception {
+ File sourceFile = new File(sourceFilePath);
+
+ // 构造 info 字典
+ Map<String, Object> infoDict = new LinkedHashMap<>();
+ infoDict.put("name", sourceFile.getName());
+ infoDict.put("length", sourceFile.length());
+ infoDict.put("piece length", PIECE_LENGTH);
+ infoDict.put("pieces", calcPiecesHashes(sourceFile));
+
+ // 构造完整 torrent 字典
+ Map<String, Object> torrentDict = new LinkedHashMap<>();
+ torrentDict.put("announce", ANNOUNCE_URL);
+ torrentDict.put("info", infoDict);
+
+ // 编码成种子数据字节数组
+ byte[] bencodedTorrent = BencodeUtils.encode(torrentDict);
+
+ // 计算 info_hash 并保存到数据库(如果需要的话)
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ byte[] infoEncoded = BencodeUtils.encode(infoDict);
+ sha1.update(infoEncoded);
+ String infoHash = bytesToHex(sha1.digest());
+
+ TorrentMeta meta = new TorrentMeta();
+ meta.setFilename(sourceFile.getName());
+ meta.setInfoHash(infoHash);
+ meta.setSize(sourceFile.length());
+ meta.setUploadTime(LocalDateTime.now());
+ torrentMetaRepository.save(meta);
+
+ return bencodedTorrent;
+ }
+
+
+ private byte[] calcPiecesHashes(File file) throws Exception {
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ try (InputStream fis = new FileInputStream(file)) {
+ byte[] buffer = new byte[PIECE_LENGTH];
+ ByteArrayOutputStream piecesBuffer = new ByteArrayOutputStream();
+
+ int read;
+ while ((read = fis.read(buffer)) > 0) {
+ sha1.reset();
+ sha1.update(buffer, 0, read);
+ piecesBuffer.write(sha1.digest());
+ }
+ return piecesBuffer.toByteArray();
+ }
+ }
+
+ private String bytesToHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt/utils/BdecodeUtils.java b/src/main/java/com/pt/utils/BdecodeUtils.java
new file mode 100644
index 0000000..c77f8b4
--- /dev/null
+++ b/src/main/java/com/pt/utils/BdecodeUtils.java
@@ -0,0 +1,84 @@
+package com.pt.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+public class BdecodeUtils {
+ public static Object decode(byte[] data) throws IOException {
+ try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
+ return decodeNext(in);
+ }
+ }
+
+ private static Object decodeNext(InputStream in) throws IOException {
+ int prefix = in.read();
+ if (prefix == -1) {
+ throw new IOException("Unexpected end of stream");
+ }
+
+ if (prefix >= '0' && prefix <= '9') {
+ // 字符串,回退一个字节给parseString处理
+ in.reset();
+ return parseString(in, prefix);
+ } else if (prefix == 'i') {
+ return parseInteger(in);
+ } else if (prefix == 'l') {
+ return parseList(in);
+ } else if (prefix == 'd') {
+ return parseDict(in);
+ } else {
+ throw new IOException("Invalid bencode prefix: " + (char) prefix);
+ }
+ }
+
+ private static String parseString(InputStream in, int firstDigit) throws IOException {
+ // 读长度前缀
+ StringBuilder lenStr = new StringBuilder();
+ lenStr.append((char) firstDigit);
+ int b;
+ while ((b = in.read()) != -1 && b != ':') {
+ lenStr.append((char) b);
+ }
+ int length = Integer.parseInt(lenStr.toString());
+
+ // 读内容
+ byte[] buf = new byte[length];
+ int read = in.read(buf);
+ if (read < length) throw new IOException("Unexpected end of stream reading string");
+ return new String(buf);
+ }
+
+ private static long parseInteger(InputStream in) throws IOException {
+ StringBuilder intStr = new StringBuilder();
+ int b;
+ while ((b = in.read()) != -1 && b != 'e') {
+ intStr.append((char) b);
+ }
+ return Long.parseLong(intStr.toString());
+ }
+
+ private static List<Object> parseList(InputStream in) throws IOException {
+ List<Object> list = new ArrayList<>();
+ int b;
+ while ((b = in.read()) != 'e') {
+ in.reset();
+ list.add(decodeNext(in));
+ }
+ return list;
+ }
+
+ private static Map<String, Object> parseDict(InputStream in) throws IOException {
+ Map<String, Object> map = new LinkedHashMap<>();
+ int b;
+ while ((b = in.read()) != 'e') {
+ in.reset();
+ String key = (String) decodeNext(in);
+ Object value = decodeNext(in);
+ map.put(key, value);
+ }
+ return map;
+ }
+}
+
diff --git a/src/main/java/com/pt/utils/BencodeUtils.java b/src/main/java/com/pt/utils/BencodeUtils.java
new file mode 100644
index 0000000..2d3850d
--- /dev/null
+++ b/src/main/java/com/pt/utils/BencodeUtils.java
@@ -0,0 +1,107 @@
+package com.pt.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class BencodeUtils {
+
+ // 通用bencode编码接口
+ public static void encode(Object obj, OutputStream out) throws IOException {
+ if (obj instanceof String) {
+ encodeString((String) obj, out);
+ } else if (obj instanceof Number) {
+ encodeInteger(((Number) obj).longValue(), out);
+ } else if (obj instanceof byte[]) {
+ encodeBytes((byte[]) obj, out);
+ } else if (obj instanceof List) {
+ encodeList((List<?>) obj, out);
+ } else if (obj instanceof Map) {
+ encodeMap((Map<String, Object>) obj, out);
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + obj.getClass());
+ }
+ }
+
+ public static byte[] encode(Object obj) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ encode(obj, baos);
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void encodeString(String s, OutputStream out) throws IOException {
+ byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
+ out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.US_ASCII));
+ out.write(':');
+ out.write(bytes);
+ }
+
+ private static void encodeBytes(byte[] bytes, OutputStream out) throws IOException {
+ out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.US_ASCII));
+ out.write(':');
+ out.write(bytes);
+ }
+
+ private static void encodeInteger(long value, OutputStream out) throws IOException {
+ out.write('i');
+ out.write(Long.toString(value).getBytes(StandardCharsets.US_ASCII));
+ out.write('e');
+ }
+
+ private static void encodeList(List<?> list, OutputStream out) throws IOException {
+ out.write('l');
+ for (Object item : list) {
+ encode(item, out);
+ }
+ out.write('e');
+ }
+
+ private static void encodeMap(Map<String, Object> map, OutputStream out) throws IOException {
+ out.write('d');
+ List<String> keys = new ArrayList<>(map.keySet());
+ Collections.sort(keys); // bencode字典必须按key排序
+ for (String key : keys) {
+ encodeString(key, out);
+ encode(map.get(key), out);
+ }
+ out.write('e');
+ }
+
+ // 构造单个compact peer的二进制格式 (4字节IP + 2字节端口)
+ public static byte[] buildCompactPeer(String ip, int port) {
+ try {
+ InetAddress addr = InetAddress.getByName(ip);
+ ByteBuffer buffer = ByteBuffer.allocate(6);
+ buffer.put(addr.getAddress());
+ buffer.putShort((short) port);
+ return buffer.array();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // 构造多个compact peer的二进制拼接
+ public static byte[] buildCompactPeers(List<String> ips, List<Integer> ports) {
+ if (ips.size() != ports.size()) throw new IllegalArgumentException("IPs and ports list size mismatch");
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (int i = 0; i < ips.size(); i++) {
+ out.write(buildCompactPeer(ips.get(i), ports.get(i)), 0, 6);
+ }
+ return out.toByteArray();
+ }
+
+ // 构造tracker响应字典,至少包含interval和peers
+ public static byte[] buildTrackerResponse(int interval, byte[] peersCompact) {
+ Map<String, Object> dict = new LinkedHashMap<>();
+ dict.put("interval", interval);
+ dict.put("peers", peersCompact);
+ return encode(dict);
+ }
+}