做种/文件转发

master
cyt 2025-08-11 17:24:05 +08:00
parent e1fc19aeab
commit c96a251bde
4 changed files with 117 additions and 517 deletions

View File

@ -0,0 +1,14 @@
package com.unisinsight.project.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/api/vdi/file/down/**").addResourceLocations("file:/var/lib/vdi/test/");
}
}

View File

@ -1,5 +1,6 @@
package com.unisinsight.project.controller; package com.unisinsight.project.controller;
import com.unisinsight.project.util.BtTorrentUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -9,103 +10,20 @@ import java.io.*;
@Slf4j @Slf4j
public class TestController { public class TestController {
private static final String BT_SCRIPT_PATH = "/var/lib/vdi/nodejs/bttorrent.sh";
private static final String RUN_USER = "java_usr"; // 替换为你的实际用户
/**
*
*
* @param sourceFile /data/file.iso
* @param torrentFile /data/file.torrent
* @return
*/
public static boolean createAndSeed(String sourceFile, String torrentFile) {
// 验证文件是否存在
if (!new File(sourceFile).exists()) {
System.err.println("源文件不存在: " + sourceFile);
return false;
}
// 确保目标目录存在
new File(torrentFile).getParentFile().mkdirs();
return executeCommand("start", sourceFile, torrentFile);
}
/**
*
*
* @param sourceFile /data/file.iso
* @return
*/
public static boolean stopSeeding(String sourceFile) {
return executeCommand("stop_path", sourceFile, null);
}
/**
* bttorrent.sh
*/
private static boolean executeCommand(String command, String arg1, String arg2) {
try {
// 构造命令
ProcessBuilder pb = new ProcessBuilder(
"sudo", "-u", RUN_USER,
BT_SCRIPT_PATH,
command,
arg1,
arg2 != null ? arg2 : "" // 处理可选参数
).redirectErrorStream(true);
// 启动进程
Process process = pb.start();
// 打印输出(调试用)
logProcessOutput(process);
// 等待执行完成
return process.waitFor() == 0;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return false;
}
}
/**
*
*/
private static void logProcessOutput(Process process) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[BT] " + line);
}
}
}
// 测试用例
public static void main(String[] args) {
String sourceFile = "/var/lib/vdi/test/example.iso";
String torrentFile = "/var/lib/vdi/test/example.torrent";
// 测试做种
System.out.println("开始做种...");
boolean seedResult = createAndSeed(sourceFile, torrentFile);
System.out.println("做种结果: " + (seedResult ? "成功" : "失败"));
// 测试停止
System.out.println("停止做种...");
boolean stopResult = stopSeeding(sourceFile);
System.out.println("停止结果: " + (stopResult ? "成功" : "失败"));
}
@GetMapping("/start") @GetMapping("/start")
public String start(@RequestParam("sourceFile")String sourceFile, public String start(@RequestParam("sourceFile") String sourceFile,
@RequestParam("torrentFile")String torrentFile) { @RequestParam("torrentFile") String torrentFile) {
System.out.println("开始做种..."); System.out.println("开始做种...");
boolean seedResult = createAndSeed(sourceFile, torrentFile); boolean seedResult = BtTorrentUtils.createAndSeed(sourceFile, torrentFile);
System.out.println("做种结果: " + (seedResult ? "成功" : "失败")); System.out.println("做种结果: " + (seedResult ? "成功" : "失败"));
return "success"; return "success";
} }
@GetMapping("/stop") @GetMapping("/stop")
public String stop(@RequestParam("sourceFile")String sourceFile) { public String stop(@RequestParam("sourceFile") String sourceFile) {
// 测试停止 // 测试停止
System.out.println("停止做种..."); System.out.println("停止做种...");
boolean stopResult = stopSeeding(sourceFile); boolean stopResult = BtTorrentUtils.stopSeeding(sourceFile);
System.out.println("停止结果: " + (stopResult ? "成功" : "失败")); System.out.println("停止结果: " + (stopResult ? "成功" : "失败"));
return "success"; return "success";
} }

View File

@ -1,429 +0,0 @@
package com.unisinsight.project.util;
import com.dampcake.bencode.BencodeInputStream;
import com.dampcake.bencode.BencodeOutputStream;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* BT
*/
public class BtTorrentUtil {
/**
* BT
*
* @param filePath
* @param trackerUrl Tracker
* @param outputFile
* @throws IOException IO
* @throws URISyntaxException URI
*/
public static void createTorrent(String filePath, String trackerUrl, String outputFile)
throws IOException, URISyntaxException {
Path path = Paths.get(filePath);
if (!Files.exists(path)) {
throw new FileNotFoundException("文件或目录不存在: " + filePath);
}
// 验证tracker URL格式
new URI(trackerUrl);
// 创建种子信息
TorrentInfo torrentInfo = new TorrentInfo();
torrentInfo.setAnnounce(trackerUrl);
torrentInfo.setCreationDate(new Date().getTime() / 1000);
torrentInfo.setCreatedBy("BtTorrentUtil v1.0");
// 处理文件信息
if (Files.isDirectory(path)) {
processDirectory(path, torrentInfo);
} else {
processFile(path, torrentInfo);
}
// 生成种子文件
writeTorrentFile(torrentInfo, outputFile);
}
/**
*
*/
private static void processFile(Path filePath, TorrentInfo torrentInfo) throws IOException {
FileInfo fileInfo = new FileInfo();
fileInfo.setLength(Files.size(filePath));
fileInfo.setPath(new ArrayList<>());
fileInfo.getPath().add(filePath.getFileName().toString());
List<FileInfo> files = new ArrayList<>();
files.add(fileInfo);
torrentInfo.setFiles(files);
torrentInfo.setName(filePath.getFileName().toString());
torrentInfo.setPieceLength(524288); // 512KB
// 计算文件的pieces
byte[] pieces = calculatePieces(filePath, torrentInfo.getPieceLength());
torrentInfo.setPieces(pieces);
}
/**
*
*/
private static void processDirectory(Path dirPath, TorrentInfo torrentInfo) throws IOException {
List<FileInfo> files = new ArrayList<>();
long totalSize = 0;
Files.walk(dirPath)
.filter(Files::isRegularFile)
.forEach(file -> {
try {
FileInfo fileInfo = new FileInfo();
fileInfo.setLength(Files.size(file));
List<String> pathList = new ArrayList<>();
// 获取相对路径
Path relativePath = dirPath.relativize(file);
for (Path part : relativePath) {
pathList.add(part.toString());
}
fileInfo.setPath(pathList);
files.add(fileInfo);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
torrentInfo.setFiles(files);
torrentInfo.setName(dirPath.getFileName().toString());
torrentInfo.setPieceLength(524288); // 512KB
// 计算所有文件的pieces简化实现
// 实际应用中需要按顺序读取所有文件数据来计算pieces
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (FileInfo file : files) {
Path filePath = dirPath;
for (String pathPart : file.getPath()) {
filePath = filePath.resolve(pathPart);
}
if (Files.exists(filePath)) {
Files.copy(filePath, baos);
}
}
byte[] pieces = calculatePiecesFromData(baos.toByteArray(), torrentInfo.getPieceLength());
torrentInfo.setPieces(pieces);
}
/**
* pieces
*/
private static byte[] calculatePieces(Path filePath, int pieceLength) throws IOException {
ByteArrayOutputStream pieces = new ByteArrayOutputStream();
byte[] buffer = new byte[pieceLength];
int bytesRead;
try (InputStream is = Files.newInputStream(filePath)) {
while ((bytesRead = is.read(buffer)) != -1) {
// 简化实现实际应使用SHA1哈希
// 这里只是示例实际应用中需要计算SHA1
byte[] hash = new byte[20]; // SHA1哈希长度为20字节
for (int i = 0; i < Math.min(20, bytesRead); i++) {
hash[i] = (byte) (buffer[i] % 256);
}
pieces.write(hash);
}
}
return pieces.toByteArray();
}
/**
* pieces
*/
private static byte[] calculatePiecesFromData(byte[] data, int pieceLength) throws IOException {
ByteArrayOutputStream pieces = new ByteArrayOutputStream();
for (int i = 0; i < data.length; i += pieceLength) {
int length = Math.min(pieceLength, data.length - i);
byte[] piece = new byte[length];
System.arraycopy(data, i, piece, 0, length);
// 简化实现实际应使用SHA1哈希
byte[] hash = new byte[20];
for (int j = 0; j < Math.min(20, length); j++) {
hash[j] = (byte) (piece[j] % 256);
}
pieces.write(hash);
}
return pieces.toByteArray();
}
/**
*
*/
private static void writeTorrentFile(TorrentInfo torrentInfo, String outputFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputFile);
BencodeOutputStream bos = new BencodeOutputStream(fos)) {
// 构建种子数据结构
java.util.Map<String, Object> torrentMap = new java.util.HashMap<>();
torrentMap.put("announce", torrentInfo.getAnnounce());
torrentMap.put("creation date", torrentInfo.getCreationDate());
torrentMap.put("created by", torrentInfo.getCreatedBy());
// info字典
java.util.Map<String, Object> infoMap = new java.util.HashMap<>();
infoMap.put("name", torrentInfo.getName());
infoMap.put("piece length", torrentInfo.getPieceLength());
infoMap.put("pieces", torrentInfo.getPieces());
if (torrentInfo.getFiles() != null && !torrentInfo.getFiles().isEmpty()) {
// 多文件模式
List<java.util.Map<String, Object>> fileList = new ArrayList<>();
for (FileInfo file : torrentInfo.getFiles()) {
java.util.Map<String, Object> fileMap = new java.util.HashMap<>();
fileMap.put("length", file.getLength());
fileMap.put("path", file.getPath());
fileList.add(fileMap);
}
infoMap.put("files", fileList);
} else {
// 单文件模式
infoMap.put("length", torrentInfo.getLength());
}
torrentMap.put("info", infoMap);
// 写入文件
bos.writeDictionary(torrentMap);
}
}
/**
*
*
* @param torrentFilePath
* @param downloadPath
* @throws IOException IO
*/
public static void downloadTorrent(String torrentFilePath, String downloadPath) throws IOException {
Path source = Paths.get(torrentFilePath);
Path target = Paths.get(downloadPath);
if (!Files.exists(source)) {
throw new FileNotFoundException("种子文件不存在: " + torrentFilePath);
}
Files.copy(source, target);
}
/**
*
*
* @param torrentFilePath
* @return
* @throws IOException IO
*/
public static TorrentInfo parseTorrent(String torrentFilePath) throws IOException {
try (FileInputStream fis = new FileInputStream(torrentFilePath);
BencodeInputStream bis = new BencodeInputStream(fis)) {
java.util.Map<String, Object> torrentMap = bis.readDictionary();
TorrentInfo torrentInfo = new TorrentInfo();
torrentInfo.setAnnounce((String) torrentMap.get("announce"));
Object creationDate = torrentMap.get("creation date");
if (creationDate instanceof Number) {
torrentInfo.setCreationDate(((Number) creationDate).longValue());
}
torrentInfo.setCreatedBy((String) torrentMap.get("created by"));
// 解析info部分
java.util.Map<String, Object> infoMap = (java.util.Map<String, Object>) torrentMap.get("info");
if (infoMap != null) {
torrentInfo.setName((String) infoMap.get("name"));
Object pieceLength = infoMap.get("piece length");
if (pieceLength instanceof Number) {
torrentInfo.setPieceLength(((Number) pieceLength).intValue());
}
torrentInfo.setPieces((byte[]) infoMap.get("pieces"));
Object length = infoMap.get("length");
if (length instanceof Number) {
torrentInfo.setLength(((Number) length).longValue());
}
// 处理文件列表
Object filesObj = infoMap.get("files");
if (filesObj instanceof List) {
List<java.util.Map<String, Object>> fileList =
(List<java.util.Map<String, Object>>) filesObj;
List<FileInfo> files = new ArrayList<>();
for (java.util.Map<String, Object> fileMap : fileList) {
FileInfo fileInfo = new FileInfo();
Object fileLength = fileMap.get("length");
if (fileLength instanceof Number) {
fileInfo.setLength(((Number) fileLength).longValue());
}
fileInfo.setPath((List<String>) fileMap.get("path"));
files.add(fileInfo);
}
torrentInfo.setFiles(files);
}
}
return torrentInfo;
}
}
/**
*
*/
public static class TorrentInfo {
private String announce;
private long creationDate;
private String createdBy;
private String name;
private long length;
private int pieceLength;
private byte[] pieces;
private List<FileInfo> files;
// Getters and Setters
public String getAnnounce() {
return announce;
}
public void setAnnounce(String announce) {
this.announce = announce;
}
public long getCreationDate() {
return creationDate;
}
public void setCreationDate(long creationDate) {
this.creationDate = creationDate;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public int getPieceLength() {
return pieceLength;
}
public void setPieceLength(int pieceLength) {
this.pieceLength = pieceLength;
}
public byte[] getPieces() {
return pieces;
}
public void setPieces(byte[] pieces) {
this.pieces = pieces;
}
public List<FileInfo> getFiles() {
return files;
}
public void setFiles(List<FileInfo> files) {
this.files = files;
}
}
/**
*
*/
public static class FileInfo {
private long length;
private List<String> path;
// Getters and Setters
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public List<String> getPath() {
return path;
}
public void setPath(List<String> path) {
this.path = path;
}
}
public static void main(String[] args) {
try {
// 创建种子文件
BtTorrentUtil.createTorrent(
"/path/to/your/file_or_directory",
"http://tracker.example.com:6969/announce",
"/path/to/output.torrent"
);
// 解析种子文件
TorrentInfo info = BtTorrentUtil.parseTorrent("/path/to/output.torrent");
System.out.println("种子名称: " + info.getName());
System.out.println("创建时间: " + new Date(info.getCreationDate() * 1000));
// 下载种子文件
BtTorrentUtil.downloadTorrent(
"/path/to/output.torrent",
"/path/to/download/location.torrent"
);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,97 @@
package com.unisinsight.project.util;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
/**
*
*/
public class BtTorrentUtils {
private static final String BT_SCRIPT_PATH = "/var/lib/vdi/nodejs/bttorrent.sh";
private static final Long WAIT_START_TIME = 8000L;
/**
*
*
* @param sourceFile /data/file.iso
* @param torrentFile /data/file.torrent
* @return
*/
public static boolean createAndSeed(String sourceFile, String torrentFile) {
// 验证文件是否存在
if (!new File(sourceFile).exists()) {
System.err.println("源文件不存在: " + sourceFile);
return false;
}
// 确保目标目录存在
new File(torrentFile).getParentFile().mkdirs();
return executeCommand("start", sourceFile, torrentFile);
}
/**
*
*
* @param sourceFile /data/file.iso
* @return
*/
public static boolean stopSeeding(String sourceFile) {
return executeCommand("stop_path", sourceFile, null);
}
/**
* bttorrent.sh
*/
private static boolean executeCommand(String command, String arg1, String arg2) {
try {
// 构造命令
ProcessBuilder pb = new ProcessBuilder(
"bash",
BT_SCRIPT_PATH,
command,
arg1,
arg2 != null ? arg2 : "" // 处理可选参数
).redirectErrorStream(true);
// 启动进程
Process process = pb.start();
// 启动新线程读取输出,避免阻塞
new Thread(() -> {
try {
logProcessOutput(process);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 对于start命令只要进程启动成功就返回true
if ("start".equals(command)) {
// 稍微等待一下看初始输出是否有错误
Thread.sleep(WAIT_START_TIME);
return process.isAlive(); // 如果进程还在运行,认为启动成功
}
// 对于stop命令仍然等待完成
return process.waitFor() == 0;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return false;
}
}
/**
*
*/
private static void logProcessOutput(Process process) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[BT输出] " + line);
}
}
}
}