From c3c8f1193978709c941003e4c77ff6893c58ef65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A4=E5=85=A8=E6=98=86?= Date: Fri, 22 Aug 2025 11:05:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=8A=8Abt=E6=9C=8D=E5=8A=A1=E5=8D=95?= =?UTF-8?q?=E7=8B=AC=E6=8B=BF=E5=87=BA=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- torrent-be/pom.xml | 152 ++++++++++++++++ .../com/unisinsight/torrent/Application.java | 15 ++ .../torrent/config/Knife4jConfig.java | 60 ++++++ .../torrent/config/RestTemplateConfig.java | 25 +++ .../torrent/config/ThreadConfig.java | 67 +++++++ .../torrent/config/WebMvcConfig.java | 14 ++ .../torrent/controller/TestController.java | 37 ++++ .../torrent/exception/BaseErrorCode.java | 172 ++++++++++++++++++ .../torrent/exception/ErrorCode.java | 13 ++ .../unisinsight/torrent/exception/Result.java | 79 ++++++++ .../torrent/util/BtTorrentUtils.java | 96 ++++++++++ torrent-be/src/main/resources/application.yml | 14 ++ 12 files changed, 744 insertions(+) create mode 100644 torrent-be/pom.xml create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/Application.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/config/Knife4jConfig.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/config/RestTemplateConfig.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/config/ThreadConfig.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/config/WebMvcConfig.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/controller/TestController.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/exception/BaseErrorCode.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/exception/ErrorCode.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/exception/Result.java create mode 100644 torrent-be/src/main/java/com/unisinsight/torrent/util/BtTorrentUtils.java create mode 100644 torrent-be/src/main/resources/application.yml diff --git a/torrent-be/pom.xml b/torrent-be/pom.xml new file mode 100644 index 0000000..13f5000 --- /dev/null +++ b/torrent-be/pom.xml @@ -0,0 +1,152 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.0 + + + + com.unisinsight + torrent-be + 1.0.0 + jar + + torrent-be + bt服务 + + + 1.8 + 3.23.4 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.apache.commons + commons-lang3 + 3.18.0 + + + cn.hutool + hutool-all + 5.8.24 + + + com.alibaba + fastjson + 2.0.49 + + + + com.dampcake + bencode + 1.4 + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + org.projectlombok + lombok + provided + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + + + + com.turn + ttorrent-core + 1.5 + + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + + net.devh + grpc-server-spring-boot-starter + 2.14.0.RELEASE + + + + junit + junit + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.6.2 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:1.50.0:exe:${os.detected.classifier} + + + ${project.basedir}/src/main/java/com/unisinsight/project/grpc/proto + + ${project.basedir}/src/main/java + false + + + + + + compile + compile-custom + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/Application.java b/torrent-be/src/main/java/com/unisinsight/torrent/Application.java new file mode 100644 index 0000000..a9774cb --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/Application.java @@ -0,0 +1,15 @@ +package com.unisinsight.torrent; + +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/torrent-be/src/main/java/com/unisinsight/torrent/config/Knife4jConfig.java b/torrent-be/src/main/java/com/unisinsight/torrent/config/Knife4jConfig.java new file mode 100644 index 0000000..b5ca2a3 --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/config/Knife4jConfig.java @@ -0,0 +1,60 @@ +package com.unisinsight.torrent.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("com.unisinsight.torrent.controller")) // 修改为你的controller包路径 + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("种子服务API") + .description("# 制作服务接口文档\n\n" + + "制作种子文件") + .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/torrent-be/src/main/java/com/unisinsight/torrent/config/RestTemplateConfig.java b/torrent-be/src/main/java/com/unisinsight/torrent/config/RestTemplateConfig.java new file mode 100644 index 0000000..9967a1a --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/config/RestTemplateConfig.java @@ -0,0 +1,25 @@ +package com.unisinsight.torrent.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * @description: + * @author: rdpnr_puzhi + * @create: 2025/08/12 + */ + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + factory.setConnectTimeout(600000); + factory.setReadTimeout(600000); + factory.setConnectionRequestTimeout(600000); + return new RestTemplate(factory); + } +} diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/config/ThreadConfig.java b/torrent-be/src/main/java/com/unisinsight/torrent/config/ThreadConfig.java new file mode 100644 index 0000000..d2d5a9a --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/config/ThreadConfig.java @@ -0,0 +1,67 @@ +package com.unisinsight.torrent.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author : ch + * @version : 1.0 + * @ClassName : ThreadConfig + * @Description : + * @DATE : Created in 16:22 2025/8/21 + *
       Copyright: Copyright(c) 2025     
+ *
       Company :   	紫光汇智信息技术有限公司		           
+ * Modification History: + * Date Author Version Discription + * -------------------------------------------------------------------------- + * 2025/08/21 ch 1.0 Why & What is modified: <修改原因描述> * + */ +@Configuration +public class ThreadConfig { + /** + * 创建用于gRPC连接监听的线程池 + * 用于处理客户端连接/断开连接等事件 + */ + @Bean("grpcConnectionListenerExecutor") + public ExecutorService grpcConnectionListenerExecutor() { + // 获取系统可用处理器核心数 + int corePoolSize =1; + int maximumPoolSize = corePoolSize * 2; + + // 设置线程空闲时间 + long keepAliveTime = 60L; + + // 设置队列大小 + int queueCapacity = 1000; + + // 创建线程池 + return new ThreadPoolExecutor( + // 核心线程数 + corePoolSize, + // 最大线程数 + maximumPoolSize, + // 空闲线程存活时间 + keepAliveTime, + // 时间单位 + TimeUnit.SECONDS, + // 任务队列 + new LinkedBlockingQueue<>(queueCapacity), + new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable runable) { + Thread thread = new Thread(runable, "grpc-connection-listener-" + threadNumber.getAndIncrement()); + thread.setDaemon(false); // 设置为非守护线程 + return thread; + } + }, + // 拒绝策略:由调用线程执行任务 + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } +} diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/config/WebMvcConfig.java b/torrent-be/src/main/java/com/unisinsight/torrent/config/WebMvcConfig.java new file mode 100644 index 0000000..35c0b01 --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/config/WebMvcConfig.java @@ -0,0 +1,14 @@ +package com.unisinsight.torrent.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/"); + } +} diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/controller/TestController.java b/torrent-be/src/main/java/com/unisinsight/torrent/controller/TestController.java new file mode 100644 index 0000000..e6826ba --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/controller/TestController.java @@ -0,0 +1,37 @@ +package com.unisinsight.torrent.controller; + +import com.unisinsight.torrent.util.BtTorrentUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Slf4j +@RequestMapping("/vdi") +@Api("种子服务") +public class TestController { + + @GetMapping("/start") + @ApiOperation(value = "开始做种") + public boolean start(@RequestParam("sourceFile") String sourceFile, + @RequestParam("torrentFile") String torrentFile) { + System.out.println("开始做种..."); + boolean seedResult = BtTorrentUtils.createAndSeed(sourceFile, torrentFile); + System.out.println("做种结果: " + (seedResult ? "成功" : "失败")); + return seedResult; + } + + @GetMapping("/stop") + @ApiOperation(value = "停止做种") + public boolean stop(@RequestParam("sourceFile") String sourceFile) { + // 测试停止 + System.out.println("停止做种..."); + boolean stopResult = BtTorrentUtils.stopSeeding(sourceFile); + System.out.println("停止结果: " + (stopResult ? "成功" : "失败")); + return stopResult; + } +} diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/exception/BaseErrorCode.java b/torrent-be/src/main/java/com/unisinsight/torrent/exception/BaseErrorCode.java new file mode 100644 index 0000000..54ebca7 --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/exception/BaseErrorCode.java @@ -0,0 +1,172 @@ +package com.unisinsight.torrent.exception; + +/** + * @author zhangmengfei [yf_zhang.mengfei@unisinsight.com] + * @description 通用错误码定义 + * @date 2020/8/10 19:53 + * @since 1.0 + */ +public enum BaseErrorCode implements ErrorCode { + INTERNAL_EXCEPTION("00001", "系统内部异常"), + INIT_EXCEPTION("00002", "系统初始化异常"), + UNKNOWN_ERROR("00003", "未知错误"), + MEMORY_ALLOCA_ERROR("00004", "内存分配失败"), + OUT_OF_MEMORY("00005", "内存溢出"), + REQUEST_TIMEOUT("00006", "请求超时"), + ILLEGAL_HANDLE("00007", "非法句柄"), + ENVIRONMENT_GET_FAIL("00008", "环境变量获取失败"), + FREQUENT_SYSTEM("00009", "系统繁忙"), + ILLEGAL_DATASOURCE("00010", "数据源异常"), + FREQUENT_OPERATION("00011", "当前操作频繁"), + UNKNOWN_REQUEST("00012", "未知的请求源"), + REQUEST_EXCEEDED("00013", "请求超过次数限制"), + CONFIG_GET_ERROR("00014", "获取配置错误"), + CONFIG_SET_ERROR("00015", "设置配置错误"), + PROGRAM_NEED_RESTART("00016", "需要重启程序"), + SYSTEM_NEED_RESTART("00017", "需要重启系统"), + CHARACTERISTICS_NOT_SUPPORT("00018", "特性不支持"), + VERIFY_FAILURE("00019", "验证失败"), + CALL_FAILURE("00020", "调用失败"), + MESSAGE_COMPONENT_EXCEPTION("00021", "消息组件异常"), + INSUFFICIENT_STORAGE_CAPACITY("00022", "存储容量不足"), + STORAGE_SERVICE_ERROR("00023", "存储服务错误"), + NETWORK_ERROR("00024", "网络错误"), + IP_ILLEGAL_ADDRESS("00025", "非法IP错误"), + IP_FORBIDDEN("00026", "被禁止的IP"), + IP_RESOLVE_DOMAIN("00027", "解析域名获取IP错误"), + PORT_ERROR("00028", "端口错误"), + API_DOMAIN_ERROR("00029", "域名错误"), + IP_ACCESS_EXCEEDED("00030", "当前IP请求超过限制"), + IP_NULL_ERROR("00031", "IP地址不能为空"), + CONNECTION_FAILURE("00032", "连接失败"), + DATA_SEND_FAILURE("00033", "数据发送失败"), + DATA_RECE_FAILURE("00034", "数据接收失败"), + HTTP_CLIENT_INIT_FAILURE("00035", "HTTP客户端初始化失败"), + HTTP_REQUEST_FAILURE("00036", "HTTP请求失败"), + HTTP_REQUEST_TIME_OUT("00037", "HTTP请求超时"), + HTTP_ERROR_CODE_400("00038", "HTTP错误码400"), + HTTP_ERROR_CODE_404("00039", "HTTP错误码404"), + HTTP_ERROR_CODE_500("500", "HTTP错误码500"), + KAFKA_TOPICE_INVALID("00041", "kafka的topic无效"), + KAFKA_MESSAGE_SEND_FAILURE("00042", " kafka消息发送失败"), + DATABASE_ERROR("00043", "数据库错误"), + DATABASE_OPERATION_FAILED("00044", "数据库操作失败"), + QUERIER_EMPTY_RESULT("00045", "查询无结果"), + ID_REQUIRE("00046", "ID不能为空"), + DATA_DUPLICATED("00047", "数据重复"), + DATA_IN_UPDATING("00048", "数据更新中"), + QUERY_RESULT_NOT_UNIQURE("00049", "查询结果不唯一"), + DICTIONARY_GET_FAILED("00050", "字典获取失败"), + INTERFACE_NOT_EXISTS("00051", "接口不存在"), + PARAMS_CHK_ERROR("00052", "参数校验错误"), + PARAMETERS_INVALID("00053", "无效入参"), + NECESSARY_PARAMS_REQURIED("00054", "必填参数缺失,核对后重试"), + TYPE_NOT_SUPPORTED("00055", "类型不支持"), + NAME_REPEATED("00056", "名称重复"), + PARAMETERS_EMPTY("00057", "参数为空"), + LANGUAGE_NOT_SUPPORT("00058", "不支持该语言"), + API_ABANDONED("00059", "接口停用"), + API_MAINTENANCE("00060", "接口维护"), + API_VER_ERROR("00061", "版本号错误"), + VERIFICATION_CODE_WRONG("00062", "验证码错误"), + FORMAT_NOT_EMPTY("00063", "%s不能为空"), + MOBILE_FORMAT_ERROR("00064", "手机号码格式错误"), + IDCARD_FORMAT_ERROR("00065", "身份证错误"), + EMAIL_FORMAT_ERROR("00066", "邮箱错误"), + LONGITUDE_LATITUDE_ERROR("00067", "经纬度错误"), + REQUEST_PATH_ERROR("00068", "请求路径错误"), + DATA_FORMAT_ERROR("00069", "数据格式错误"), + TIME_PARAMS_ERROR("00070", "时间参数错误"), + CAR_LICENSE_ERROR("00071", "车牌号错误"), + CAMERA_NUMBER_ERROR("00072", "摄像头编号错误"), + CALLBACK_ADDR_ERROR("00073", "回调地址URL错误"), + TIME_FORMAT_ERROR("00074", "时间格式必须是ISO-8601"), + PAGE_NUM_OVERLIMIT("00075", "页码超出限制"), + PAGE_SIZE_OVERLIMIT("00076", "分页大小超出限制"), + THRID_PARTY_INTERFACE_FAILURE("00077", "调用第三方接口失败"), + JSON_PARSE_ERROR("00078", "json解析错误"), + JSON_MESSAGE_FIELD_TYPE_WRONG("00079", "json消息字段类型错误"), + JSON_MESSAGE_FIELD_VALUE_WRONG("00080", "json消息字段取值错误"), + JSON_MESSAGE_FIELD_MISSING("00081", "json消息字段缺失"), + CREATING_JSON_OBJECT_FAILED("00082", "创建json对象失败"), + CREATING_JSON_ARRAY_FAILED("00083", "创建json数组失败"), + JSON_FORMAT_CONTENT_WRONG("00084", "json格式或内容错误"), + FILENAME_ERROR("00085", "文件名错误"), + FILE_ADDRESS_ERROR("00086", "文件地址不存在"), + FILE_ID_NOT_EXIST("00087", "文件ID不存在"), + FILE_ID_LONG("00088", "文件ID过长"), + FILE_TYPE_WRONG("00089", "文件类型错误"), + FILE_CREATE_FAILED("00090", "创建文件失败"), + FILE_OPEN_FAILED("00091", "打开文件失败"), + FILE_DOWNLOAD_FAILED("00092", "下载文件失败"), + FILE_READ_FAILED("00093", "读取文件失败"), + FILE_WRITE_FAILED("00094", "写入文件失败"), + FILE_FORMAT_ERROR("00095", "文件格式错误"), + FILE_ENCRYPTED("00096", "文件被加密"), + EXEL_GENERATE_FAILED("00097", "EXEL生成失败"), + EXEL_IMPORT_FAILED("00098", "EXEL导入失败"), + TEXT_ENCODING_CONVERSION_FAILED("00099", "文本编码转换失败"), + FILE_NOT_RECOGNIZED("00100", "未识别文件"), + FILE_SIZE_EXCEEDS_LIMIT("00101", "文件大小超过限制"), + FILE_UPLOAD_FAILED("00102", "文件上传失败"), + OTHER_USER_UPLOAD_FILE("00103", "其他用户正在上传该文件"), + USER_NOT_FOUND("00104", "用户不存在"), + USER_NOT_LOGIN("00105", "用户未登录"), + USER_NOT_PERMISSON("00106", "用户没权限"), + PASSWORD_ERROR("00107", "密码错误"), + USER_NAME_EXIST("00108", "用户名已存在"), + USER_NAME_WRONG_LENGTH("00109", "用户名长度错误"), + PASSWORD_WRONG_LENGTH("00110", "密码长度错误"), + USER_NAME_OR_PASSWORD_WRONG_FORMAT("00111", "用户名或密码格式错误"), + USER_NAME_OR_PASSWORD_EMPTY("00112", "用户名或密码为空"), + USER_LOGIN_EXPIRED("00113", "用户登录已过期"), + SECURITY_AUTHENTICATION_FAILED("00114", "安全认证失败"), + ACCOUNT_NUMBER_LOCKED("00115", "账号被锁定"), + USER_ADD_FAILED("00116", "添加用户失败"), + USER_DELETE_FAILED("00117", "删除用户失败"), + USER_UPDATE_FAILED("00118", "修改用户失败"), + USER_QUERY_FAILED("00119", "查询用户失败"), + PASSWORD_UPDATE_FAILED("00120", "修改密码失败"), + PASSWORD_NEW_WRONG("00121", "新密码错误"), + PASSWORD_NEW_WRONG_LENGTH("00122", "新密码长度错误"), + PASSWORD_OLD_WRONG_LENGTH("00123", "原密码长度错误"), + PASSWORD_OLD_WRONG("00124", "原密码错误"), + PASSWORD_OLD_NEW_SAME("00125", "新密码和原密码相同"), + USER_NAME_OR_PASSWORD_INCORRECT("00126", "用户名或密码错误,需重新配置"), + DEVICE_GET_FAILED("00127", "设备获取失败"), + CHANNEL_GET_FAILED("00128", "通道获取失败"), + DEVICE_OFFLINE("00129", "设备离线"), + RESOURCES_TREE_GET_FAILED("00130", "资源树获取失败"), + RESOURCES_EXIST("00131", "资源已经存在"), + DEVICE_NOT_EXIST("00132", "设备不存在"), + DEVICE_EXIST("00133", "设备已经存在"), + DEVICE_NUMBER_REACHE_MAX("00134", "设备数达到上限"), + CHANNEL_NUMBER_REACHE_MAX("00135", "设备数达到上限"), + CAMERA_ID_WRONG("00136", "相机ID错误"), + CAMERA_NAME_WRONG("00137", "相机名错误"), + IPC_DEVICE_CODE_EXCEEDS_LONG("00138", "IPC设备编码超长"), + IPC_DEVICE_NAME_EXCEEDS_LONG("00139", "IPC设备名称超长"), + IPC_TYPE_GET_FAILED("00140", "获取IPC款型失败"), + FORMAT_VALIDATE_ERROR("00141", "%s"), + EXPORT_ERROR("00142", "导出EXCEL失败"), + SUCCESS("200", "处理成功"); + + private String code; + private String message; + + BaseErrorCode(String code, String message) { + this.code = code; + this.message = message; + } + + + @Override + public String getCode() { + return this.code; + } + + @Override + public String getMessage() { + return this.message; + } +} diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/exception/ErrorCode.java b/torrent-be/src/main/java/com/unisinsight/torrent/exception/ErrorCode.java new file mode 100644 index 0000000..22492c6 --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/exception/ErrorCode.java @@ -0,0 +1,13 @@ +package com.unisinsight.torrent.exception; + +/** + * @author zhangmengfei [yf_zhang.mengfei@unisinsight.com] + * @description 错误码接口 + * @date 2020/8/10 19:53 + * @since 1.0 + */ +public interface ErrorCode { + String getCode(); + + String getMessage(); +} \ No newline at end of file diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/exception/Result.java b/torrent-be/src/main/java/com/unisinsight/torrent/exception/Result.java new file mode 100644 index 0000000..36bb03e --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/exception/Result.java @@ -0,0 +1,79 @@ +package com.unisinsight.torrent.exception; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author zhangmengfei [yf_zhang.mengfei@unisinsight.com] + * @description 返回结果类 + * @date 2020/8/10 19:53 + * @since 1.0 + */ +@Data +@ApiModel("返回结果类") +public class Result { + + /** + * 返回码 + */ + @ApiModelProperty("返回码") + @JsonProperty(value = "code") + private String code; + + /** + * 返回信息 + */ + @ApiModelProperty("返回信息") + @JsonProperty(value = "message") + private String message; + + /** + * 返回数据 + */ + @ApiModelProperty("返回数据") + @JsonProperty(value = "data") + private T data; + + public Result() { + } + + public Result(String code, String message) { + this.code = code; + this.message = message; + } + + public Result(String code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + public static Result successResult(T data) { + return new Result<>(BaseErrorCode.SUCCESS.getCode(), BaseErrorCode.SUCCESS.getMessage(), data); + } + + public static Result successResult() { + return new Result<>(BaseErrorCode.SUCCESS.getCode(), BaseErrorCode.SUCCESS.getMessage()); + } + + public static Result errorResult(ErrorCode errorCode) { + return new Result<>(errorCode.getCode(), errorCode.getMessage()); + } + + public static Result errorResult(ErrorCode errorCode, T data) { + return new Result<>(errorCode.getCode(), errorCode.getMessage(), data); + } + + public static Result errorResultMessage(ErrorCode errorCode, String message) { + return new Result<>(errorCode.getCode(), message); + } + + /** + * 判断当前返回是否成功 + */ + public boolean success() { + return BaseErrorCode.SUCCESS.getCode().equals(this.code); + } +} diff --git a/torrent-be/src/main/java/com/unisinsight/torrent/util/BtTorrentUtils.java b/torrent-be/src/main/java/com/unisinsight/torrent/util/BtTorrentUtils.java new file mode 100644 index 0000000..ea0ffdd --- /dev/null +++ b/torrent-be/src/main/java/com/unisinsight/torrent/util/BtTorrentUtils.java @@ -0,0 +1,96 @@ +package com.unisinsight.torrent.util; + +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); + } + } + } +} diff --git a/torrent-be/src/main/resources/application.yml b/torrent-be/src/main/resources/application.yml new file mode 100644 index 0000000..930cc19 --- /dev/null +++ b/torrent-be/src/main/resources/application.yml @@ -0,0 +1,14 @@ +# application.yml +server: + port: 8114 + +spring: + servlet: + multipart: + max-file-size: 25MB + max-request-size: 25MB + +knife4j: + production: false + basic: + enable: false \ No newline at end of file