diff --git a/nex-be/Dockerfile b/nex-be/Dockerfile new file mode 100644 index 0000000..0a77b4f --- /dev/null +++ b/nex-be/Dockerfile @@ -0,0 +1,14 @@ +# 使用官方OpenJDK作为基础镜像 +FROM openjdk:11-jre-slim + +# 设置工作目录 +WORKDIR /app + +# 复制项目的jar文件到容器中 +COPY target/*.jar app.jar + +# 暴露应用端口 +EXPOSE 8080 + +# 启动应用 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/nex-be/pom.xml b/nex-be/pom.xml new file mode 100644 index 0000000..0bc65f1 --- /dev/null +++ b/nex-be/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.0 + + + + com.example + chunked-upload-demo + 1.0.0 + jar + + nex-be + 开箱即用管理端 + + + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + com.dampcake + bencode + 1.4 + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/nex-be/src/main/java/com/unisinsight.project/Application.java b/nex-be/src/main/java/com/unisinsight.project/Application.java new file mode 100644 index 0000000..29e1534 --- /dev/null +++ b/nex-be/src/main/java/com/unisinsight.project/Application.java @@ -0,0 +1,15 @@ +package com.unisinsight.project; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 分片上传应用启动类 + */ +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/nex-be/src/main/java/com/unisinsight.project/config/Knife4jConfig.java b/nex-be/src/main/java/com/unisinsight.project/config/Knife4jConfig.java new file mode 100644 index 0000000..3d707bb --- /dev/null +++ b/nex-be/src/main/java/com/unisinsight.project/config/Knife4jConfig.java @@ -0,0 +1,67 @@ +package com.unisinsight.project.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +@Import(BeanValidatorPluginsConfiguration.class) +@EnableWebMvc +public class Knife4jConfig implements WebMvcConfigurer { + + @Bean + public Docket defaultApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .groupName("默认分组") + .select() + .apis(RequestHandlerSelectors.basePackage("org.hz.controller")) // 修改为你的controller包路径 + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("文件分片上传服务API") + .description("# 大文件分片上传接口文档\n\n" + + "## 功能说明\n" + + "1. 支持大文件分片上传\n" + + "2. 支持断点续传\n" + + "3. 自动合并完整文件\n\n" + + "## 使用流程\n" + + "1. 前端将大文件分片\n" + + "2. 依次上传各分片\n" + + "3. 系统自动合并文件") + .termsOfServiceUrl("http://localhost:8080/") + .contact(new Contact("API开发者", "https://example.com", "developer@example.com")) + .version("1.0.0") + .build(); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 配置Swagger UI和WebJars资源的访问路径,使得这些静态资源可以通过特定的URL路径在Web应用中被访问 + registry.addResourceHandler("doc.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + + } + +} diff --git a/nex-be/src/main/java/com/unisinsight.project/controller/FileChunkController.java b/nex-be/src/main/java/com/unisinsight.project/controller/FileChunkController.java new file mode 100644 index 0000000..fcc76ef --- /dev/null +++ b/nex-be/src/main/java/com/unisinsight.project/controller/FileChunkController.java @@ -0,0 +1,264 @@ +package com.unisinsight.project.controller; + +import io.swagger.annotations.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 大文件分片上传控制器 + */ +@RestController +@RequestMapping("/api/files") +@Api(tags = "文件分片上传接口") +public class FileChunkController { + + // 临时目录,用于存储上传的分片 + @Value("${file.upload.temp-dir:${java.io.tmpdir}/chunked-uploads}") + private String tempDir; + + // 最终文件存储目录 + @Value("${file.upload.dir:${user.home}/uploads}") + private String uploadDir; + + // 存储每个文件的分片信息 + private final Map fileUploadMap = new ConcurrentHashMap<>(); + + /** + * 上传文件分片 + * + * @param chunk 分片文件 + * @param fileId 文件唯一标识符 + * @param chunkNumber 当前分片编号(从1开始) + * @param totalChunks 总分片数 + * @param fileName 原始文件名 + * @param totalSize 文件总大小 + * @return 上传结果 + */ + @PostMapping("/upload-chunk") + @ApiOperation(value = "上传文件分片", notes = "上传单个文件分片,当所有分片上传完成后自动合并文件") + @ApiImplicitParams({ + @ApiImplicitParam(name = "chunk", value = "文件分片", required = true, dataType = "__File", paramType = "form"), + @ApiImplicitParam(name = "fileId", value = "文件唯一标识符", required = true, dataType = "String", paramType = "query"), + @ApiImplicitParam(name = "chunkNumber", value = "当前分片编号(从1开始)", required = true, dataType = "int", paramType = "query"), + @ApiImplicitParam(name = "totalChunks", value = "总分片数", required = true, dataType = "int", paramType = "query"), + @ApiImplicitParam(name = "fileName", value = "原始文件名", required = true, dataType = "String", paramType = "query"), + @ApiImplicitParam(name = "totalSize", value = "文件总大小", required = true, dataType = "long", paramType = "query") + }) + @ApiResponses({ + @ApiResponse(code = 200, message = "上传成功"), + @ApiResponse(code = 500, message = "服务器内部错误") + }) + public ResponseEntity> uploadChunk( + @RequestParam("chunk") MultipartFile chunk, + @RequestParam("fileId") String fileId, + @RequestParam("chunkNumber") int chunkNumber, + @RequestParam("totalChunks") int totalChunks, + @RequestParam("fileName") String fileName, + @RequestParam("totalSize") long totalSize) { + + Map response = new HashMap<>(); + + try { + // 创建临时目录 + Path fileTempDir = Paths.get(tempDir, fileId); + if (!Files.exists(fileTempDir)) { + Files.createDirectories(fileTempDir); + } + + // 保存分片文件 + String chunkFileName = String.format("%05d.part", chunkNumber); + Path chunkFilePath = fileTempDir.resolve(chunkFileName); + chunk.transferTo(chunkFilePath); + + // 更新文件上传信息 + FileUploadInfo uploadInfo = fileUploadMap.computeIfAbsent(fileId, + id -> new FileUploadInfo(id, fileName, totalChunks, totalSize)); + uploadInfo.addUploadedChunk(chunkNumber); + + // 检查是否所有分片都已上传 + if (uploadInfo.isUploadComplete()) { + // 合并文件 + Path finalDir = Paths.get(uploadDir); + if (!Files.exists(finalDir)) { + Files.createDirectories(finalDir); + } + + Path finalFilePath = finalDir.resolve(fileName); + mergeChunks(fileId, finalFilePath, totalChunks); + + // 清理临时文件 + cleanupTempFiles(fileId); + + // 从上传映射中移除 + fileUploadMap.remove(fileId); + + response.put("status", "completed"); + response.put("message", "文件上传并合并完成"); + response.put("filePath", finalFilePath.toString()); + } else { + response.put("status", "uploading"); + response.put("message", "分片上传成功"); + response.put("uploadedChunks", uploadInfo.getUploadedChunks().size()); + response.put("totalChunks", totalChunks); + } + + response.put("success", true); + return ResponseEntity.ok(response); + + } catch (Exception e) { + response.put("success", false); + response.put("message", "上传失败: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + /** + * 查询文件上传状态 + * + * @param fileId 文件唯一标识符 + * @return 上传状态信息 + */ + @GetMapping("/upload-status/{fileId}") + @ApiOperation("查询文件上传状态") + public ResponseEntity> getUploadStatus(@PathVariable String fileId) { + Map response = new HashMap<>(); + + FileUploadInfo uploadInfo = fileUploadMap.get(fileId); + if (uploadInfo == null) { + // 检查文件是否已经完成上传并合并 + try { + Path finalFilePath = Paths.get(uploadDir, fileId); + if (Files.exists(finalFilePath)) { + response.put("status", "completed"); + response.put("message", "文件上传已完成"); + response.put("filePath", finalFilePath.toString()); + } else { + response.put("status", "not_found"); + response.put("message", "文件上传信息不存在"); + } + } catch (Exception e) { + response.put("status", "error"); + response.put("message", "查询状态失败: " + e.getMessage()); + } + } else { + response.put("status", "uploading"); + response.put("uploadedChunks", uploadInfo.getUploadedChunks().size()); + response.put("totalChunks", uploadInfo.getTotalChunks()); + response.put("progress", (double) uploadInfo.getUploadedChunks().size() / uploadInfo.getTotalChunks()); + } + + response.put("success", true); + return ResponseEntity.ok(response); + } + + /** + * 合并所有分片文件 + * + * @param fileId 文件唯一标识符 + * @param outputPath 合并后的文件路径 + * @param totalChunks 总分片数 + * @throws IOException IO异常 + */ + private void mergeChunks(String fileId, Path outputPath, int totalChunks) throws IOException { + try (OutputStream outputStream = Files.newOutputStream(outputPath)) { + Path fileTempDir = Paths.get(tempDir, fileId); + + // 按顺序合并分片 + for (int i = 1; i <= totalChunks; i++) { + String chunkFileName = String.format("%05d.part", i); + Path chunkPath = fileTempDir.resolve(chunkFileName); + + if (!Files.exists(chunkPath)) { + throw new IOException("缺少分片文件: " + chunkFileName); + } + + // 将分片内容追加到输出文件 + Files.copy(chunkPath, outputStream); + } + } + } + + /** + * 清理临时分片文件 + * + * @param fileId 文件唯一标识符 + * @throws IOException IO异常 + */ + private void cleanupTempFiles(String fileId) throws IOException { + Path fileTempDir = Paths.get(tempDir, fileId); + if (Files.exists(fileTempDir)) { + // 递归删除临时目录及其内容 + Files.walk(fileTempDir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + /** + * 文件上传信息类 + */ + private static class FileUploadInfo { + private final String fileId; + private final String fileName; + private final int totalChunks; + private final long totalSize; + private final Set uploadedChunks; + + public FileUploadInfo(String fileId, String fileName, int totalChunks, long totalSize) { + this.fileId = fileId; + this.fileName = fileName; + this.totalChunks = totalChunks; + this.totalSize = totalSize; + this.uploadedChunks = ConcurrentHashMap.newKeySet(); + } + + public void addUploadedChunk(int chunkNumber) { + uploadedChunks.add(chunkNumber); + } + + public boolean isUploadComplete() { + return uploadedChunks.size() == totalChunks; + } + + public String getFileId() { + return fileId; + } + + public String getFileName() { + return fileName; + } + + public int getTotalChunks() { + return totalChunks; + } + + public long getTotalSize() { + return totalSize; + } + + public Set getUploadedChunks() { + return uploadedChunks; + } + } + + @GetMapping("/test") + @ApiOperation("测试") + public ResponseEntity getUploadStatus() { + return ResponseEntity.ok("ok"); + } +} diff --git a/nex-be/src/main/java/com/unisinsight.project/util/BtTorrentUtil.java b/nex-be/src/main/java/com/unisinsight.project/util/BtTorrentUtil.java new file mode 100644 index 0000000..7699a86 --- /dev/null +++ b/nex-be/src/main/java/com/unisinsight.project/util/BtTorrentUtil.java @@ -0,0 +1,429 @@ +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 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 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 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 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 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()) { + // 多文件模式 + java.util.List> fileList = new ArrayList<>(); + for (FileInfo file : torrentInfo.getFiles()) { + java.util.Map 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 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 infoMap = (java.util.Map) 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 java.util.List) { + java.util.List> fileList = + (java.util.List>) filesObj; + + List files = new ArrayList<>(); + for (java.util.Map fileMap : fileList) { + FileInfo fileInfo = new FileInfo(); + + Object fileLength = fileMap.get("length"); + if (fileLength instanceof Number) { + fileInfo.setLength(((Number) fileLength).longValue()); + } + + fileInfo.setPath((List) 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 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 getFiles() { + return files; + } + + public void setFiles(List files) { + this.files = files; + } + } + + /** + * 文件信息类 + */ + public static class FileInfo { + private long length; + private List path; + + // Getters and Setters + public long getLength() { + return length; + } + + public void setLength(long length) { + this.length = length; + } + + public List getPath() { + return path; + } + + public void setPath(List 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" + ); + + // 解析种子文件 + BtTorrentUtil.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(); + } + } +} + + + diff --git a/nex-be/src/main/resources/application.yml b/nex-be/src/main/resources/application.yml new file mode 100644 index 0000000..befcef1 --- /dev/null +++ b/nex-be/src/main/resources/application.yml @@ -0,0 +1,18 @@ +# application.yml +server: + port: 8112 +file: + upload: + temp-dir: /tmp/chunked-uploads + dir: /uploads + +spring: + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + +knife4j: + production: false + basic: + enable: false \ No newline at end of file