Compare commits

...

27 Commits

Author SHA1 Message Date
唐潇凯
97ffb19c03 机器代理管理规范化 2025-06-06 16:41:13 +08:00
唐潇凯
2d4653b29a 机器环境变量管理规范化 2025-06-06 16:05:29 +08:00
唐潇凯
66bc6a1250 机器代理管理规范化 2025-06-06 16:05:05 +08:00
唐潇凯
926c1af1e1 机器环境变量管理规范化 2025-06-06 11:37:11 +08:00
唐潇凯
f28fe4ecdf 机器环境变量管理规范化 2025-06-06 11:35:57 +08:00
唐潇凯
56150c156b Merge remote-tracking branch 'origin/jenkins-engin' into jenkins-engin 2025-06-05 11:01:56 +08:00
唐潇凯
891a332c59 机器连接修改 2025-06-05 11:00:54 +08:00
zyj
7cd4396001 机器管理代码格式化提交,修改新增环境变量逻辑 2025-06-04 17:11:00 +08:00
唐潇凯
62b0caa3da 阿里云oss 2025-06-04 14:50:33 +08:00
唐潇凯
4815b590c4 Merge remote-tracking branch 'origin/jenkins-engin' into jenkins-engin
# Conflicts:
#	modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/SecretKeyController.java
#	modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/SecretKeyService.java
#	modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/SecretKeyServiceImpl.java
#	modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/AliOssUtil.java
2025-06-04 14:45:15 +08:00
唐潇凯
a238538106 密钥模块修改测试完毕 2025-06-04 14:40:30 +08:00
zyj
ea35ed1f0b 机器管理代码格式化提交 2025-06-04 14:19:29 +08:00
zyj
c26a183209 Merge remote-tracking branch 'origin/jenkins-engin' into jenkins-engin 2025-06-04 11:03:55 +08:00
zyj
4b8dfe978a 机器管理调试更改 2025-06-04 11:03:35 +08:00
唐潇凯
30c201a230 sql修改 2025-06-03 14:22:43 +08:00
唐潇凯
818c2e0198 Merge remote-tracking branch 'origin/jenkins-engin' into jenkins-engin 2025-06-03 14:14:54 +08:00
唐潇凯
a7398d265c 代码测试完成 2025-06-03 14:13:39 +08:00
zyj
04afb442cf 机器管理测试更改 2025-06-03 14:03:04 +08:00
唐潇凯
b65bdd95bc get方法修改 2025-06-03 09:35:34 +08:00
唐潇凯
790193d0c9 controller返回值统一,机器相关实体类修改 2025-05-31 14:16:20 +08:00
蒲先生
1028cc0705 调整完毕 2025-05-31 09:58:28 +08:00
蒲先生
a7738d2add 变更包名 2025-05-30 17:23:48 +08:00
蒲先生
1d8a1f6eb9 清除无需要的 2025-05-30 16:57:03 +08:00
蒲先生
db95a112bc 清理 2025-05-30 16:37:31 +08:00
蒲先生
5410bd4374 机器管理迁移到ops-pro 2025-05-30 16:30:16 +08:00
唐潇凯
6642fc493b 机器管理sql 2025-05-30 15:33:09 +08:00
唐潇凯
6e317f9b8c 机器管理 2025-05-30 14:08:16 +08:00
52 changed files with 3706 additions and 5 deletions

3
.idea/compiler.xml generated
View File

@ -7,6 +7,7 @@
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="machine-management-module" />
</profile>
<profile name="Annotation profile for ops-pro" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
@ -53,6 +54,7 @@
<module name="module-ci-dispatch-api" target="17" />
<module name="module-ci-environment" target="17" />
<module name="module-ci-event" target="17" />
<module name="module-ci-execute" target="1.5" />
<module name="module-ci-log" target="17" />
<module name="module-ci-market" target="17" />
<module name="module-ci-project" target="17" />
@ -69,6 +71,7 @@
<module name="app-plugins" options="-parameters" />
<module name="commons" options="-parameters" />
<module name="framework" options="-parameters" />
<module name="machine-management-module" options="-parameters" />
<module name="module-ci-commons" options="-parameters" />
<module name="module-ci-engine" options="-parameters" />
<module name="module-ci-plugin" options="-parameters" />

1
.idea/encodings.xml generated
View File

@ -39,6 +39,7 @@
<file url="file://$PROJECT_DIR$/framework/spring-boot-starter-websocket/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/framework/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/framework/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/modules/ee/machine-management-module/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/modules/module-ci-common-pipeline/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/modules/module-ci-common-pipeline/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/modules/module-ci-common/src/main/java" charset="UTF-8" />

View File

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="huaweicloud" />
<option name="name" value="huawei" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="huaweicloud" />
<option name="name" value="huawei" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
<remote-repository>
<option name="id" value="aliyunmaven" />
<option name="name" value="aliyun" />

1
.idea/misc.xml generated
View File

@ -52,6 +52,7 @@
<option value="$PROJECT_DIR$/modules/module-ci-store-api/pom.xml" />
<option value="$PROJECT_DIR$/modules/module-ci-process-biz/pom.xml" />
<option value="$PROJECT_DIR$/modules/module-ci-process-api/pom.xml" />
<option value="$PROJECT_DIR$/modules/ee/machine-management-module/pom.xml" />
</list>
</option>
<option name="ignoredFiles">

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cd.casic.boot</groupId>
<artifactId>modules</artifactId>
<version>${revision}</version>
</parent>
<artifactId>module-ci-machine</artifactId>
<packaging>jar</packaging>
<version>${revision}</version>
<name>${project.artifactId}</name>
<dependencies>
<dependency>
<groupId>cd.casic.boot</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<!-- 机器连接-->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringDoc OpenAPI 依赖 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!--文件上传-->
<dependency>
<groupId>cd.casic.boot</groupId>
<artifactId>module-infra-biz</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,33 @@
package cd.casic.module.machine.configuration;
import cd.casic.module.infra.framework.file.core.client.s3.S3FileClientConfig;
import cd.casic.module.machine.utils.AliYunOssClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AliYunOssConfig extends S3FileClientConfig{
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKey;
@Value("${aliyun.oss.accessKeySecret}")
private String accessSecret;
@Value("${aliyun.oss.bucketName}")
private String bucket;
// 定义 S3 客户端 Bean
@Bean
public AliYunOssClient aliYunClient() {
// 创建配置对象
S3FileClientConfig config = new AliYunOssConfig();
config.setEndpoint(endpoint);
config.setAccessKey(accessKey);
config.setAccessSecret(accessSecret);
config.setBucket(bucket);
AliYunOssClient aliYunOssClient = new AliYunOssClient(1L, config);
// 创建并返回客户端实例
aliYunOssClient.init();
return aliYunOssClient;
}
}

View File

@ -0,0 +1,33 @@
package cd.casic.module.machine.contants;
import cd.casic.framework.commons.exception.ErrorCode;
/**
* 机器报错
*/
public interface MachineErrorCodeConstants {
// ========== 机器模块 1-003-000-000 ==========
ErrorCode MACHINE_INFO_NULL = new ErrorCode(1_003_000_000, "机器信息为空");
ErrorCode UPLOADING_FILE_FAIL = new ErrorCode(1_003_000_001, "上传文件失败");
ErrorCode DOWNLOAD_FILE_FAIL = new ErrorCode(1_003_000_002, "下载失败");
ErrorCode FILENAME_NULL = new ErrorCode(1_003_000_003, "文件名为空");
ErrorCode READ_FILE_FAIL = new ErrorCode(1_003_000_004, "读取文件失败");
ErrorCode DELETE_FILE_FAIL = new ErrorCode(1_003_000_005, "删除文件失败");
ErrorCode PARAMETER_ERROR = new ErrorCode(1_003_000_008, "参数错误");
// ========== 机器环境变量模块 1-003-000-009 ==========
ErrorCode MACHINE_ENV_NULL = new ErrorCode(1_003_000_009, "机器环境变量为空");
ErrorCode MACHINE_ENV_NOT_EXISTS = new ErrorCode(1_003_000_009, "机器不存在");
ErrorCode MACHINE_ENV_KEY_ILLEGAL = new ErrorCode(1_003_000_010, "机器环境变量键不合法");
// ========== 机器代理模块 1-003-000-006 ==========
ErrorCode MACHINE_PROXY_HOST_IP_NULL = new ErrorCode(1_003_000_006, "机器代理主机地址为空");
ErrorCode MACHINE_PROXY_USER_NAME_NULL = new ErrorCode(1_003_000_006, "机器代理用户名为空");
ErrorCode MACHINE_PROXY_NOT_EXISTS = new ErrorCode(1_003_000_007, "机器代理不存在");
ErrorCode MACHINE_PROXY_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_007, "机器代理类型不存在");
ErrorCode MACHINE_PROXY_IS_ONLINE = new ErrorCode(1_003_000_007, "机器代理在线,不能删除");
ErrorCode OSS_PARAM_NULL = new ErrorCode(1_003_000_011, "oss参数无法读取");
ErrorCode SECRETKEY_NULL = new ErrorCode(1_003_000_012, "密钥为空");
}

View File

@ -0,0 +1,83 @@
package cd.casic.module.machine.controller;
import cd.casic.framework.commons.pojo.CommonResult;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.commons.util.object.BeanUtils;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import cd.casic.module.machine.service.MachineEnvService;
import cd.casic.module.machine.controller.vo.MachineEnvVO;
import cn.hutool.core.collection.CollUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import static cd.casic.framework.commons.pojo.CommonResult.success;
/**
* 环境变量控制器
*/
@Tag(name = "环境变量管理")
@RestController
@RequestMapping("/ci/machineEnv")
@Validated
public class MachineEnvController {
@Resource
private MachineEnvService machineEnvService;
@PostMapping("/create")
@Operation(summary = "新增环境变量")
@PreAuthorize("@ss.hasPermission('ci:machineEnv:create')")
public CommonResult<Long> createEnv(@Valid @RequestBody MachineEnvVO machineEnvVO) {
Long id = machineEnvService.createEnv(machineEnvVO);
return success(id);
}
@PutMapping("/update")
@Operation(summary = "修改环境变量")
@PreAuthorize("@ss.hasPermission('ci:machineEnv:update')")
public CommonResult<Boolean> updateEnv(@Valid@RequestBody MachineEnvVO machineEnvVO) {
machineEnvService.updateEnv(machineEnvVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除机器的环境变量")
@PreAuthorize("@ss.hasPermission('ci:machineEnv:delete')")
public CommonResult<Boolean> deleteEnv(@RequestParam("id") Long id) {
machineEnvService.deleteEnv(id);
return success(true);
}
@DeleteMapping("/deleteList")
@Operation(summary = "批量删除机器环境变量")
@PreAuthorize("@ss.hasPermission('ci:machineEnv:delete')")
public CommonResult<Boolean> deleteEnvList(@RequestParam("ids") String ids) {
machineEnvService.deleteEnvList(ids);
return success(true);
}
@GetMapping("/getEnv")
@Operation(summary = "获取机器的环境变量")
public CommonResult<MachineEnvVO> getEnv(@RequestParam("id") Long id) {
MachineEnvVO machineEnvVO = machineEnvService.getEnv(id);
return success(machineEnvVO);
}
@PostMapping("/list")
@Operation(summary = "获取环境变量列表")
public CommonResult<PageResult<MachineEnvVO>> getEnvPage(@Valid @RequestBody MachineEnvVO machineEnvVO) {
PageResult<MachineEnvDO> pageResult = machineEnvService.getEnvPage(machineEnvVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal()));
}
return success(BeanUtils.toBean(pageResult, MachineEnvVO.class));
}
}

View File

@ -0,0 +1,126 @@
//package cd.casic.module.machine.controller;
//
//import cd.casic.framework.commons.pojo.CommonResult;
//import cd.casic.framework.commons.pojo.PageResult;
//import cd.casic.module.machine.dal.dataobject.MachineInfo;
//import cd.casic.module.machine.enums.ConnectionStatus;
//import cd.casic.module.machine.service.MachineInfoService;
//import cd.casic.module.machine.controller.vo.MachineInfoDto;
//import io.swagger.v3.oas.annotations.Operation;
//import io.swagger.v3.oas.annotations.tags.Tag;
//import jakarta.annotation.Resource;
//import org.springframework.web.bind.annotation.*;
//
//import java.util.Map;
//
//import static cd.casic.framework.commons.pojo.CommonResult.success;
//
//@RestController
//@Tag(name = "机器信息管理")
//@RequestMapping("/api/machineInfo")
//public class MachineInfoController {
// @Resource
// private MachineInfoService machineInfoService;
//
// @PostMapping("/add")
// @Operation(summary = "新增机器信息")
// public CommonResult<Boolean> add(@RequestBody MachineInfoDto machineInfoDto) {
// return success(machineInfoService.addMachineInfo(machineInfoDto));
// }
//
//
// @PostMapping("/list")
// @Operation(summary = "获取机器信息列表")
// public CommonResult<PageResult<MachineInfoDto>> list(@RequestBody MachineInfoDto machineInfoDto) {
// return success(machineInfoService.listMachineInfo(machineInfoDto));
// }
//
// @PutMapping("/update")
// @Operation(summary = "编辑机器信息")
// public CommonResult<Boolean> update(@RequestBody MachineInfoDto machineInfoDto) {
// return success(machineInfoService.updateMachineInfo(machineInfoDto));
// }
//
// @PutMapping("/updateStatus")
// @Operation(summary = "机器启用/停用")
// public CommonResult<Boolean> updateStatus(@RequestBody MachineInfoDto machineInfoDto) {
// return success(machineInfoService.updateStatus(machineInfoDto));
// }
//
// @PutMapping("/bindingSecretKey")
// @Operation(summary = "绑定密钥")
// public CommonResult<Boolean> bindingSecretKey(@RequestBody MachineInfoDto machineInfoDto) {
// return success(machineInfoService.bindingSecretKey(machineInfoDto));
// }
//
// @DeleteMapping("/delete")
// @Operation(summary = "机器信息删除")
// public CommonResult<Boolean> delete(@RequestParam Long machineInfoId) {
// machineInfoService.deleteMachineInfo(machineInfoId);
// return success(true);
// }
//
// @DeleteMapping("/deleteList")
// @Operation(summary = "批量删除机器信息")
// public CommonResult<Boolean> deleteList(@RequestParam String machineInfoIds) {
// machineInfoService.deleteList(machineInfoIds);
// return success(true);
// }
//
// @PostMapping("/test")
// @Operation(summary = "测试机器连接")
// public CommonResult<Boolean> testConnection(@RequestParam Long id) {
// return success(machineInfoService.testConnection(id));
// }
//
// @GetMapping("/status/{machineName}")
// @Operation(summary = "获取机器连接状态")
// public CommonResult<ConnectionStatus> getConnectionStatus(@PathVariable String machineName) {
// return success(machineInfoService.getConnectionStatus(machineName));
// }
//
// @GetMapping("/status/all")
// @Operation(summary = "获取所有机器连接状态")
// public CommonResult<Map<String, ConnectionStatus>> getAllConnectionStatus() {
// return success(machineInfoService.getAllConnectionStatus());
// }
//
// @PostMapping("/connect")
// @Operation(summary = "建立机器连接")
// public CommonResult<String> connect(@RequestBody MachineInfo machineInfo) {
// return success(machineInfoService.connect(machineInfo));
// }
//
// @PostMapping("/disconnect/{sessionId}")
// @Operation(summary = "断开机器连接")
// public CommonResult<Boolean> disconnect(@PathVariable String sessionId) {
// return success(machineInfoService.disconnect(sessionId));
// }
//
// @PostMapping("/execute/{sessionId}")
// @Operation(summary = "执行远程命令")
// public CommonResult<String> executeCommand(
// @PathVariable String sessionId,
// @RequestBody String command) {
//
// return success(machineInfoService.executeCommand(sessionId, command));
// }
//
// @PostMapping("/upload/{sessionId}")
// @Operation(summary = "上传文件到远程机器")
// public CommonResult<Boolean> uploadFile(
// @PathVariable String sessionId,
// @RequestParam String localFilePath,
// @RequestParam String remoteFilePath) {
// return success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath));
// }
//
// @PostMapping("/download/{sessionId}")
// @Operation(summary = "从远程机器下载文件")
// public CommonResult<Boolean> downloadFile(
// @PathVariable String sessionId,
// @RequestParam String remoteFilePath,
// @RequestParam String localFilePath) {
// return success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath));
// }
//}

View File

@ -0,0 +1,72 @@
package cd.casic.module.machine.controller;
import cd.casic.framework.commons.pojo.CommonResult;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.commons.util.object.BeanUtils;
import cd.casic.module.machine.controller.vo.MachineProxyVO;
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
import cd.casic.module.machine.service.MachineProxyService;
import cn.hutool.core.collection.CollUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import static cd.casic.framework.commons.pojo.CommonResult.success;
/**
* 机器代理控制器
*/
@RestController
@RequestMapping("/ci/machineProxy")
@Tag(name = "机器代理管理")
@Validated
public class MachineProxyController {
@Resource
private MachineProxyService machineProxyService;
@PostMapping("/create")
@Operation(summary = "注册新的机器代理")
@PreAuthorize("@ss.hasPermission('ci:machineProxy:create')")
public CommonResult<Long> createProxy(@Valid @RequestBody MachineProxyVO machineProxyVO) {
Long id = machineProxyService.createProxy(machineProxyVO);
return success(id);
}
@PutMapping("/update")
@Operation(summary = "修改代理")
@PreAuthorize("@ss.hasPermission('ci:machineProxy:update')")
public CommonResult<Boolean> updateProxy(@Valid @RequestBody MachineProxyVO machineProxyVO) {
machineProxyService.updateProxy(machineProxyVO);
return success(true);
}
@PostMapping("/list")
@Operation(summary = "获取代理列表")
public CommonResult<PageResult<MachineProxyVO>> getProxyPage(@Valid @RequestBody MachineProxyVO machineProxyVO) {
PageResult<MachineProxyDO> pageResult = machineProxyService.getProxyPage(machineProxyVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal()));
}
return success(BeanUtils.toBean(pageResult, MachineProxyVO.class));
}
@GetMapping("/allStatus")
@Operation(summary = "获取所有代理的状态统计")
public CommonResult<Map<Integer, Long>> getStatusStatistics() {
return success(machineProxyService.getAllProxyStatus());
}
@DeleteMapping("/deleteList")
@Operation(summary = "批量删除代理")
@PreAuthorize("@ss.hasPermission('ci:machineProxy:delete')")
public CommonResult<Boolean> deleteProxyList(@RequestParam String ids) {
machineProxyService.deleteProxyList(ids);
return success(true);
}
}

View File

@ -0,0 +1,56 @@
//package cd.casic.module.machine.controller;
//
//import cd.casic.framework.commons.pojo.CommonResult;
//import cd.casic.framework.commons.pojo.PageResult;
//import cd.casic.module.machine.dal.dataobject.SecretKey;
//import cd.casic.module.machine.service.SecretKeyService;
//import cd.casic.module.machine.controller.vo.SecretKeyDto;
//import io.swagger.v3.oas.annotations.Operation;
//import io.swagger.v3.oas.annotations.tags.Tag;
//import jakarta.annotation.Resource;
//import org.springframework.web.bind.annotation.*;
//
//import java.util.List;
//
//import static cd.casic.framework.commons.pojo.CommonResult.success;
//
//@RestController
//@RequestMapping("/api/secretKey")
//@Tag(name = "密钥管理")
//public class SecretKeyController {
// @Resource
// private SecretKeyService secretKeyService;
//
// @PostMapping(value = "/add")
// @Operation(summary = "新增密钥")
// public CommonResult<Boolean> add(@RequestBody SecretKeyDto secretKeyDto) throws Exception {
// return success(secretKeyService.addSecretKey(secretKeyDto));
// }
//
// @PutMapping("/bindingMachine")
// @Operation(summary = "绑定机器")
// public CommonResult<Boolean> bindingMachine(@RequestParam("secretKeyId") Long secretKeyId, @RequestParam("machineIds") List<Long> machineIds) {
// secretKeyService.bindingMachine(secretKeyId, machineIds);
// return success(true);
// }
//
// @PutMapping("/update")
// @Operation(summary = "编辑密钥信息")
// public CommonResult<Boolean> update(@RequestBody SecretKeyDto secretKeyDto) {
// return success(secretKeyService.updateSecretKey(secretKeyDto));
// }
//
// @DeleteMapping("/deleteList")
// @Operation(summary = "批量删除密钥")
// public CommonResult<Boolean> deleteList(@RequestParam("secretKeyId") List<Long> secretKeyIds) {
// return success(secretKeyService.deleteList(secretKeyIds));
// }
//
// @PostMapping("/list")
// @Operation(summary = "获取密钥信息列表")
// public CommonResult<PageResult<SecretKey>> list(@RequestBody SecretKeyDto secretKeyDto) {
// return success(secretKeyService.listSecretKey(secretKeyDto));
// }
//
//
//}

View File

@ -0,0 +1,36 @@
package cd.casic.module.machine.controller.vo;
import cd.casic.framework.commons.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Schema(description = "管理后台 - 机器环境变量信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true) // 添加链式调用支持
public class MachineEnvVO extends PageParam{
@Schema(description = "环境变量ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "环境变量键", requiredMode = Schema.RequiredMode.REQUIRED, example = "JAVA_HOME")
private String envKey;
@Schema(description = "环境变量值", requiredMode = Schema.RequiredMode.REQUIRED, example = "/usr/java/jdk1.8.0_271")
private String envValue;
@Schema(description = "环境变量描述", example = "Java运行环境路径")
private String description;
@Schema(description = "关联的机器ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long machineId;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00")
private LocalDateTime createTime;
@Schema(description = "更新时间", example = "2023-06-15T10:30:00")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,46 @@
package cd.casic.module.machine.controller.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MachineInfoDto extends PageDto {
private Long id;
private Date createDate;
private Date updateDate;
private String name;
private String tag;
private String hostIp;
private String description;
private String username;
private String status;
private Integer sshPort;
private String password;
private Long secretKeyId;
private Long machineProxyId;
private String authenticationType;
private String machineInfoType;
private int isProxy;
}

View File

@ -0,0 +1,50 @@
package cd.casic.module.machine.controller.vo;
import cd.casic.framework.commons.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 机器代理信息 Response VO")
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class MachineProxyVO extends PageParam {
@Schema(description = "代理ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
private String username;
@Schema(description = "代理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "HTTP")
private Integer proxyType;
@Schema(description = "版本号", example = "1.0.0")
private String version;
@Schema(description = "状态ONLINE:在线OFFLINE:离线)", requiredMode = Schema.RequiredMode.REQUIRED, example = "ONLINE")
private String status;
@Schema(description = "描述信息", example = "用于生产环境的代理服务器")
private String description;
@Schema(description = "主机IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100")
private String hostIp;
@Schema(description = "SSH端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "22")
private String sshPort;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00")
private LocalDateTime createTime;
@Schema(description = "更新时间", example = "2023-06-15T10:30:00")
private LocalDateTime updateTime;
@Schema(description = "密码", example = "******")
private String password;
}

View File

@ -0,0 +1,35 @@
package cd.casic.module.machine.controller.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SecretKeyDto extends PageDto {
private Long id;
private String name;
private String description;
//存储路径,本地上传文件路径
private String path;
private String fileName;
//密钥密码
private String password;
private Date createDate;
private Date updateDate;
private List<Long> machineInfoIds;
}

View File

@ -0,0 +1,24 @@
package cd.casic.module.machine.convert;
import cd.casic.module.machine.controller.vo.MachineEnvVO;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MachineEnvConvert {
MachineEnvConvert INSTANCE = Mappers.getMapper(MachineEnvConvert.class);
// 转换实体为VO
default MachineEnvVO convertToVO(MachineEnvDO machineEnvDO) {
MachineEnvVO VO = new MachineEnvVO();
VO.setId(machineEnvDO.getId());
VO.setEnvKey(machineEnvDO.getEnvKey());
VO.setEnvValue(machineEnvDO.getEnvValue());
VO.setDescription(machineEnvDO.getDescription());
VO.setCreateTime(machineEnvDO.getCreateTime());
VO.setUpdateTime(machineEnvDO.getUpdateTime());
return VO;
}
}

View File

@ -0,0 +1,57 @@
package cd.casic.module.machine.dal.dataobject;
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 环境变量实体类
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("machine_env")
public class MachineEnvDO extends BaseDO {
/**
* 环境变量id
*/
@TableId
private Long id;
/**
* 机器ID唯一关联
*/
private Long machineId;
/**
* 环境变量键
*/
private String envKey;
/**
* 环境变量值
*/
private String envValue;
/**
* 描述信息
*/
private String description;
}

View File

@ -0,0 +1,69 @@
package cd.casic.module.machine.dal.dataobject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import cd.casic.module.machine.enums.AuthenticationType;
import cd.casic.module.machine.enums.MachineInfoStatus;
import cd.casic.module.machine.enums.MachineInfoType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "machine_info")
public class MachineInfo extends BaseEntity {
@TableField(value = "name")
private String name;
@TableField(value = "tag")
private String tag;
@TableField(value = "host_ip")
private String hostIp;
@TableField(value = "description")
private String description;
@TableField(exist = false)
private MachineInfoType machineInfoType;
@TableField(value = "machine_info_type_code")
private int machineInfoTypeCode;
@TableField(exist = false)
private MachineInfoStatus status;
@TableField(value = "status_code")
private int statusCode;
//用户名
@TableField(value = "username")
private String username;
//SSH端口号
@TableField(value = "ssh_port")
private Integer sshPort;
@TableField(value = "password")
private String password;
@TableField(value = "secret_key_id")
private Long secretKeyId;
@TableField(value = "machine_proxy_id")
private Long machineProxyId;
@TableField(value = "authentication_type_code")
private int authenticationTypeCode;
@TableField(exist = false)
private AuthenticationType authenticationType;
@TableField(value = "is_proxy")
private int isProxy;
}

View File

@ -0,0 +1,58 @@
package cd.casic.module.machine.dal.dataobject;
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
import cd.casic.module.machine.enums.MachineProxyStatus;
import com.baomidou.mybatisplus.annotation.*;
import cd.casic.module.machine.enums.MachineProxyType;
import lombok.*;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* 机器代理实体类
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("machine_proxy")
public class MachineProxyDO extends BaseDO {
/**
* 代理id
*/
@TableId
private Long id;
@TableField(value = "host_ip")
private String hostIp;
@TableField(value = "ssh_port")
private String sshPort;
//todo 字典
@TableField(value = "proxy_type")
private int proxyType;
@TableField(value = "version")
private String version;
//todo 字典
@TableField(value = "status")
private int status;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "description")
private String description;
}

View File

@ -0,0 +1,32 @@
package cd.casic.module.machine.dal.dataobject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "machine_secret_key")
public class SecretKey extends BaseEntity{
@TableField(value = "name")
private String name;
@TableField(value = "description")
private String description;
//oss存储路径
@TableField(value = "path")
private String path;
@TableField
private String fileName;
//密钥密码
@TableField(value = "password")
private String password;
}

View File

@ -0,0 +1,22 @@
package cd.casic.module.machine.dal.mysql;
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
import cd.casic.module.machine.controller.vo.MachineEnvVO;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
* 环境变量Mapper接口
*/
@Mapper
public interface MachineEnvMapper extends BaseMapperX<MachineEnvDO> {
default PageResult<MachineEnvDO>selectPage(MachineEnvVO machineEnvVO){
return selectPage(machineEnvVO,new LambdaQueryWrapperX<MachineEnvDO>()
.likeIfPresent(MachineEnvDO::getEnvKey, machineEnvVO.getEnvKey())
.likeIfPresent(MachineEnvDO::getDescription, machineEnvVO.getDescription()));
}
}

View File

@ -0,0 +1,11 @@
package cd.casic.module.machine.dal.mysql;
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import cd.casic.module.machine.dal.dataobject.MachineInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MachineInfoMapper extends BaseMapperX<MachineInfo> {
}

View File

@ -0,0 +1,26 @@
package cd.casic.module.machine.dal.mysql;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
import cd.casic.module.machine.controller.vo.MachineProxyVO;
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 机器代理Mapper接口
*/
@Mapper
public interface MachineProxyMapper extends BaseMapperX<MachineProxyDO> {
default PageResult<MachineProxyDO> selectPage(MachineProxyVO machineProxyVO) {
return selectPage(machineProxyVO, new LambdaQueryWrapperX<MachineProxyDO>()
.eqIfPresent(MachineProxyDO::getHostIp, machineProxyVO.getHostIp())
.eqIfPresent(MachineProxyDO::getProxyType, machineProxyVO.getProxyType())
.eqIfPresent(MachineProxyDO::getStatus, machineProxyVO.getStatus())
.likeIfPresent(MachineProxyDO::getDescription, machineProxyVO.getDescription())
);
}
}

View File

@ -0,0 +1,9 @@
package cd.casic.module.machine.dal.mysql;
import cd.casic.module.machine.dal.dataobject.SecretKey;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SecretServiceMapper extends BaseMapper<SecretKey> {
}

View File

@ -0,0 +1,16 @@
package cd.casic.module.machine.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AuthenticationType implements CodeEnum {
PASSWORD(1,"密码认证"),
SECRET_KEY(2,"密钥认证");
private final int code;
private final String message;
}

View File

@ -0,0 +1,7 @@
package cd.casic.module.machine.enums;
public interface CodeEnum {
int getCode();
String getMessage();
}

View File

@ -0,0 +1,22 @@
package cd.casic.module.machine.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 连接状态枚举
*/
@Getter
@AllArgsConstructor
public enum ConnectionStatus {
DISCONNECTED("断开连接"),
CONNECTING("正在连接"),
CONNECTED("已连接"),
AUTH_FAILED("认证失败"),
CONNECTION_TIMEOUT("连接超时"),
CONNECTION_ERROR("连接错误"),
CLOSED("已关闭");
private final String description;
}

View File

@ -0,0 +1,16 @@
package cd.casic.module.machine.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MachineInfoStatus implements CodeEnum {
ENABLE(1,"启用"),
UN_ENABLE(0,"停用");
private final int code;
private final String message;
}

View File

@ -0,0 +1,16 @@
package cd.casic.module.machine.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MachineInfoType implements CodeEnum {
Linux(1,"Linux"),
WINDOWS(2,"Windows");
private final int code;
private final String message;
}

View File

@ -0,0 +1,32 @@
package cd.casic.module.machine.enums;
import cd.casic.framework.commons.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum MachineProxyStatus implements IntArrayValuable {
/**
* 代理状态 (online, offline, installing, updating, error)
*/
ONLINE(1, "online"),
OFFLINE(2, "offline"),
INSTALLING(3, "installing"),
UPDATING(4, "updating"),
ERROR(5, "error");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineProxyStatus::getCode).toArray();
private final int code;
private final String message;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,30 @@
package cd.casic.module.machine.enums;
import cd.casic.framework.commons.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum MachineProxyType implements IntArrayValuable {
HTTP(1, "http"),
SOCKS4(2, "socks4"),
SOCKS5(3, "socks5");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineProxyType::getCode).toArray();
/**
* 状态值
*/
private final Integer code;
/**
* 状态名
*/
private final String message;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,19 @@
package cd.casic.module.machine.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum PermissionExceptionEnum implements CodeEnum {
URL_NOT_EXIST(1, "资源路径不存在,请检查请求地址"),
NO_PERMISSION(2, "没有权限访问资源,请联系管理员"),
NO_PERMISSION_OPERATE(3, "没有权限操作该数据,请联系管理员");
private final int code;
private final String message;
}

View File

@ -0,0 +1,639 @@
//package cd.casic.module.machine.handler;
//import cd.casic.module.machine.utils.AliYunOssClient;
//import cd.casic.module.machine.dal.dataobject.MachineInfo;
//import cd.casic.module.machine.dal.dataobject.SecretKey;
//import cd.casic.module.machine.enums.ConnectionStatus;
//import cd.casic.module.machine.service.SecretKeyService;
//import com.jcraft.jsch.*;
//import jakarta.annotation.Resource;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.stereotype.Component;
//import org.springframework.util.StreamUtils;
//import org.springframework.util.StringUtils;
//import java.io.*;
//import java.nio.charset.StandardCharsets;
//import java.util.Objects;
//import java.util.Properties;
//import java.util.concurrent.atomic.AtomicBoolean;
//
///**
// * 优化后的SSH连接会话类
// */
//@Slf4j
//@Component
//public class ConnectionSession implements AutoCloseable {
// @Resource
// SecretKeyService secretKeyService;
//
// @Resource
// AliYunOssClient aliYunOssClient;
//
// private MachineInfo machineInfo;
// private Session sshSession;
// private ConnectionStatus status = ConnectionStatus.DISCONNECTED;
// private final AtomicBoolean isExecuting = new AtomicBoolean(false);
//
// // todo连接配置常量
// private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒)
// private static final int COMMAND_TIMEOUT = 30000; // 命令执行超时时间(毫秒)
// private static final int RETRY_COUNT = 3; // 重试次数
// private static final int RETRY_DELAY = 1000; // 重试间隔(毫秒)
//
// public ConnectionSession() {
//
// }
//
// // 使用setter注入MachineInfo
// public void setMachineInfo(MachineInfo machineInfo) {
// this.machineInfo = Objects.requireNonNull(machineInfo, "MachineInfo cannot be null");
// log.debug("MachineInfo 已注入: {}", machineInfo.getHostIp());
// }
//
//
//
// /**
// * 建立SSH连接支持重试机制
// */
// public synchronized void connect() throws JSchException {
// if (status == ConnectionStatus.CONNECTED) {
// log.debug("Already connected to {}", machineInfo.getHostIp());
// return;
// }
//
// status = ConnectionStatus.CONNECTING;
// JSchException lastException = null;
//
// for (int attempt = 1; attempt <= RETRY_COUNT; attempt++) {
// try {
// doConnect();
// status = ConnectionStatus.CONNECTED;
// log.info("SSH connection established successfully to {} (attempt {}/{})",
// machineInfo.getHostIp(), attempt, RETRY_COUNT);
// return;
// } catch (JSchException e) {
// lastException = e;
// status = ConnectionStatus.CONNECTION_ERROR;
// log.error("SSH connection attempt {}/{} failed: {}",
// attempt, RETRY_COUNT, e.getMessage());
//
// // 认证失败直接退出无需重试
// if (e.getMessage().contains("Auth fail")) {
// status = ConnectionStatus.AUTH_FAILED;
// throw e;
// }
//
// // 重试前等待
// if (attempt < RETRY_COUNT) {
// try {
// Thread.sleep(RETRY_DELAY);
// } catch (InterruptedException ie) {
// Thread.currentThread().interrupt();
// throw new JSchException("Connection attempt interrupted", ie);
// }
// }
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
//
// // 所有重试都失败
// throw new JSchException("Failed to connect after " + RETRY_COUNT + " attempts", lastException);
// }
//
// /**
// * 实际执行连接逻辑
// */
// private void doConnect() throws JSchException, IOException {
// JSch jsch = new JSch();
//
// // 配置认证方式
// configureAuthentication(jsch);
//
// // 创建SSH会话
// sshSession = jsch.getSession(
// machineInfo.getUsername(),
// machineInfo.getHostIp(),
// machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22
// );
//
// // 配置连接参数
// configureSession(sshSession);
//
// // 建立连接
// sshSession.connect(CONNECTION_TIMEOUT);
// }
//
// /**
// * 配置认证方式密码或密钥
// */
// private void configureAuthentication(JSch jsch) throws JSchException {
// if (machineInfo.getAuthenticationTypeCode() == 2) {
// // 密钥认证
// if (machineInfo.getSecretKeyId() == null) {
// throw new JSchException("Secret key ID is required for key-based authentication");
// }
//
// String privateKeyContent = getPrivateKeyContent(machineInfo.getSecretKeyId());
// // 验证私钥格式
// if (!privateKeyContent.startsWith("-----BEGIN")) {
// throw new JSchException("Invalid private key format. Expected OpenSSH format.");
// }
//
// try {
// // 尝试加载私钥
// jsch.addIdentity(
// machineInfo.getName(),
// privateKeyContent.getBytes(StandardCharsets.UTF_8),
// null,
// null
// );
// log.info("Private key loaded successfully for {}", machineInfo.getHostIp());
// } catch (JSchException e) {
// log.error("Failed to load private key: {}", e.getMessage());
// throw e;
// }
// } else if (machineInfo.getAuthenticationTypeCode() == 1) {
// // 密码认证
// if (StringUtils.isEmpty(machineInfo.getPassword())) {
// throw new JSchException("Password is required for password-based authentication");
// }
// } else {
// throw new JSchException("Unsupported authentication type: " + machineInfo.getAuthenticationType());
// }
// }
//
// /**
// * 配置SSH会话参数安全增强
// */
// private void configureSession(Session session) {
// Properties config = new Properties();
//
// // 安全增强默认验证主机密钥
// if (isTrustedEnvironment()) {
// log.warn("Running in trusted environment - disabling strict host key checking for {}",
// machineInfo.getHostIp());
// config.put("StrictHostKeyChecking", "no");
// } else {
// config.put("StrictHostKeyChecking", "yes");
// // 可选配置已知主机文件路径
// //直接配置阿里云密钥地址
// config.put("UserKnownHostsFile", secretKeyService.getById(machineInfo.getSecretKeyId()).getPath());
// }
//
// // 其他安全配置
// config.put("PreferredAuthentications", "publicKey,password,keyboard-interactive");
// config.put("ServerAliveInterval", "30"); // 每30秒发送一次心跳
// config.put("ServerAliveCountMax", "3"); // 允许3次心跳失败
//
// session.setConfig(config);
// }
//
// /**
// * 判断是否为可信环境生产环境应返回false
// */
// private boolean isTrustedEnvironment() {
// // todo实际项目中应基于配置或环境变量判断
// return System.getProperty("environment", "production").equalsIgnoreCase("development");
// }
//
// @Override
// public synchronized void close() {
// disconnect();
// }
//
// public synchronized void disconnect() {
// if (sshSession != null && sshSession.isConnected()) {
// try {
// sshSession.disconnect();
// log.info("SSH connection closed: {}", machineInfo.getHostIp());
// } catch (Exception e) {
// log.error("Error closing SSH session: {}", e.getMessage());
// }
// }
// status = ConnectionStatus.DISCONNECTED;
// }
//
// /**
// * 执行远程命令支持超时和中断处理
// */
// public String executeCommand(String command) throws JSchException, IOException {
// if (!isConnected()) {
// throw new IllegalStateException("Session is not connected");
// }
//
// if (!isExecuting.compareAndSet(false, true)) {
// throw new IllegalStateException("Another command is already executing");
// }
//
// Channel channel = null;
// InputStream inputStream = null;
// ByteArrayOutputStream outputStream = null;
//
// try {
// channel = createExecChannel(command);
// inputStream = channel.getInputStream();
// outputStream = new ByteArrayOutputStream();
//
// // 连接通道并设置超时
// channel.connect(COMMAND_TIMEOUT);
//
// // 读取命令输出
// return readCommandOutput(inputStream, outputStream, channel);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// throw new IOException("Command execution interrupted", e);
// } finally {
// // 释放资源
// closeResources(channel, inputStream, outputStream);
// isExecuting.set(false);
// }
// }
//
// /**
// * 创建并配置命令执行通道
// */
// private Channel createExecChannel(String command) throws JSchException {
// Channel channel = sshSession.openChannel("exec");
// ((ChannelExec) channel).setCommand(command);
//
// // 配置通道
// channel.setInputStream(null);
// ((ChannelExec) channel).setErrStream(new ByteArrayOutputStream()); // 捕获错误输出
//
// return channel;
// }
//
// /**
// * 读取命令输出
// */
// private String readCommandOutput(InputStream inputStream,
// ByteArrayOutputStream outputStream,
// Channel channel) throws IOException, InterruptedException {
// byte[] buffer = new byte[1024];
//
//
// // 使用线程中断机制实现超时控制
// Thread readerThread = new Thread(() -> {
// int bytesRead;
// try {
// while ((bytesRead = inputStream.read(buffer)) != -1) {
// outputStream.write(buffer, 0, bytesRead);
// }
// } catch (IOException e) {
// // 通道关闭或读取异常
// if (channel.isConnected()) {
// log.warn("Error reading command output: {}", e.getMessage());
// }
// }
// });
//
// readerThread.start();
//
// // 等待命令执行完成或超时
// readerThread.join(COMMAND_TIMEOUT);
//
// // 如果线程仍在运行中断并关闭通道
// if (readerThread.isAlive()) {
// readerThread.interrupt();
// channel.disconnect();
// throw new IOException("Command execution timed out after " + COMMAND_TIMEOUT + "ms");
// }
//
// // 等待通道完全关闭
// while (channel.isConnected()) {
// Thread.sleep(100);
// }
//
// return outputStream.toString(StandardCharsets.UTF_8);
// }
//
// /**
// * 关闭资源
// */
// private void closeResources(Channel channel, InputStream inputStream, OutputStream outputStream) {
// if (outputStream != null) {
// try {
// outputStream.close();
// } catch (IOException e) {
// log.warn("Error closing output stream: {}", e.getMessage());
// }
// }
//
// if (inputStream != null) {
// try {
// inputStream.close();
// } catch (IOException e) {
// log.warn("Error closing input stream: {}", e.getMessage());
// }
// }
//
// if (channel != null && channel.isConnected()) {
// channel.disconnect();
// }
// }
//
// /**
// * 上传文件到远程服务器
// */
// public boolean uploadFile(String localFilePath, String remoteFilePath) throws IOException {
// if (!isConnected()) {
// throw new IllegalStateException("Cannot upload file: SSH session is not connected");
// }
//
// // 检查本地文件是否存在且可读
// File localFile = new File(localFilePath);
// if (!localFile.exists()) {
// throw new FileNotFoundException("Local file not found: " + localFilePath);
// }
// if (!localFile.canRead()) {
// throw new IOException("Cannot read local file: " + localFilePath);
// }
//
// ChannelSftp channel = null;
// boolean uploadSuccess = false;
//
// try {
// // 创建并连接SFTP通道设置超时
// channel = (ChannelSftp) sshSession.openChannel("sftp");
// channel.connect(CONNECTION_TIMEOUT);
//
// // 确保目标目录存在
// createRemoteDirectoryIfNotExists(channel, getParentDirectory(remoteFilePath));
//
// // 使用更健壮的上传方式
// channel.put(
// new FileInputStream(localFile),
// remoteFilePath,
// new ProgressMonitorAdapter(localFile.length()),
// ChannelSftp.OVERWRITE
// );
//
// uploadSuccess = true;
// log.info("File uploaded successfully: {} -> {}", localFilePath, remoteFilePath);
// return true;
//
// } catch (SftpException e) {
// log.error("SFTP error during file upload ({} -> {}): {}",
// localFilePath, remoteFilePath, e.getMessage(), e);
// throw new IOException("SFTP error: " + e.getMessage(), e);
// } catch (FileNotFoundException e) {
// log.error("Local file not found during upload: {}", localFilePath, e);
// throw e;
// } catch (IOException e) {
// log.error("IO error during file upload: {}", e.getMessage(), e);
// throw e;
// } catch (Exception e) {
// log.error("Unexpected error during file upload: {}", e.getMessage(), e);
// throw new IOException("Unexpected error: " + e.getMessage(), e);
// } finally {
// // 确保通道始终被关闭
// disconnectChannel(channel);
//
//// // 如果上传失败尝试删除不完整的文件
//// if (!uploadSuccess && remoteFilePath != null && !remoteFilePath.isEmpty()) {
//// tryDeleteIncompleteFile(remoteFilePath);
//// }
// }
// }
//
// public boolean downloadFile(String remoteFilePath, String localFilePath) throws IOException {
// if (!isConnected()) {
// throw new IllegalStateException("Cannot download file: SSH session is not connected");
// }
//
// // 检查本地目录是否存在且可写
// File localFile = new File(localFilePath);
// File parentDir = localFile.getParentFile();
// if (parentDir != null && !parentDir.exists()) {
// if (!parentDir.mkdirs()) {
// throw new IOException("Failed to create local directory: " + parentDir.getAbsolutePath());
// }
// }
// if (parentDir != null && !parentDir.canWrite()) {
// throw new IOException("Cannot write to local directory: " + parentDir.getAbsolutePath());
// }
//
// ChannelSftp channel = null;
// boolean downloadSuccess = false;
// File tempFile = null;
//
// try {
// // 创建并连接SFTP通道设置超时
// channel = (ChannelSftp) sshSession.openChannel("sftp");
// channel.connect(CONNECTION_TIMEOUT);
//
// // 检查远程文件是否存在
// SftpATTRS attrs = channel.stat(remoteFilePath);
// long fileSize = attrs.getSize();
//
// // 使用临时文件避免部分下载覆盖完整文件
// tempFile = new File(localFilePath + ".part");
//
// // 执行下载并监控进度
// channel.get(
// remoteFilePath,
// new FileOutputStream(tempFile).toString(),
// new ProgressMonitorAdapter(fileSize),
// ChannelSftp.OVERWRITE
// );
//
// // 验证下载完整性
// if (tempFile.length() != fileSize) {
// throw new IOException("Download incomplete: expected " + fileSize +
// " bytes, but got " + tempFile.length() + " bytes");
// }
//
// // 重命名临时文件为目标文件原子操作
// if (!tempFile.renameTo(localFile)) {
// throw new IOException("Failed to rename temporary file to: " + localFilePath);
// }
//
// downloadSuccess = true;
// log.info("File downloaded successfully: {} -> {}", remoteFilePath, localFilePath);
// return true;
//
// } catch (SftpException e) {
// log.error("SFTP error during file download ({} -> {}): {}",
// remoteFilePath, localFilePath, e.getMessage(), e);
// if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
// throw new FileNotFoundException("Remote file not found: " + remoteFilePath);
// }
// throw new IOException("SFTP error: " + e.getMessage(), e);
// } catch (IOException e) {
// log.error("IO error during file download: {}", e.getMessage(), e);
// throw e;
// } catch (Exception e) {
// log.error("Unexpected error during file download: {}", e.getMessage(), e);
// throw new IOException("Unexpected error: " + e.getMessage(), e);
// } finally {
// // 确保通道始终被关闭
// disconnectChannel(channel);
//
// // 如果下载失败清理临时文件
// if (!downloadSuccess && tempFile != null && tempFile.exists()) {
// if (tempFile.delete()) {
// log.debug("Deleted incomplete temporary file: {}", tempFile.getAbsolutePath());
// } else {
// log.warn("Failed to delete incomplete temporary file: {}", tempFile.getAbsolutePath());
// }
// }
// }
// }
//
// // 创建远程目录如果不存在
// private void createRemoteDirectoryIfNotExists(ChannelSftp channel, String directory) throws SftpException {
// if (directory == null || directory.isEmpty() || directory.equals("/")) {
// return;
// }
//
// try {
// channel.stat(directory);
// } catch (SftpException e) {
// // 目录不存在尝试创建
// createRemoteDirectoryIfNotExists(channel, getParentDirectory(directory));
// channel.mkdir(directory);
// log.debug("Created remote directory: {}", directory);
// }
// }
//
// // 获取路径的父目录
// private String getParentDirectory(String path) {
// int lastSlash = path.lastIndexOf('/');
// return lastSlash > 0 ? path.substring(0, lastSlash) : "";
// }
//
// // 断开SFTP通道
// private void disconnectChannel(Channel channel) {
// if (channel != null && channel.isConnected()) {
// try {
// channel.disconnect();
// log.debug("SFTP channel disconnected");
// } catch (Exception e) {
// log.warn("Error disconnecting SFTP channel: {}", e.getMessage());
// }
// }
// }
//
// // 尝试删除不完整的文件
// private void tryDeleteIncompleteFile(String remoteFilePath) {
// ChannelSftp channel = null;
// try {
// channel = (ChannelSftp) sshSession.openChannel("sftp");
// channel.connect(CONNECTION_TIMEOUT);
// channel.rm(remoteFilePath);
// log.info("Deleted incomplete file: {}", remoteFilePath);
// } catch (Exception e) {
// log.warn("Failed to delete incomplete file {}: {}", remoteFilePath, e.getMessage());
// } finally {
// disconnectChannel(channel);
// }
// }
//
// // 增强的进度监控器
// private static class ProgressMonitorAdapter implements SftpProgressMonitor {
// private final long totalBytes;
// private long bytesWritten = 0;
// private int lastProgress = 0;
// private final long startTime = System.currentTimeMillis();
//
// public ProgressMonitorAdapter(long totalBytes) {
// this.totalBytes = totalBytes;
// }
//
// @Override
// public boolean count(long count) {
// bytesWritten += count;
//
// // 计算进度百分比
// int progress = (int) ((bytesWritten * 100) / totalBytes);
//
// // 每10%或每秒更新一次日志
// long elapsedTime = System.currentTimeMillis() - startTime;
// if (progress - lastProgress >= 10 || elapsedTime >= 1000) {
// double speed = bytesWritten / (elapsedTime / 1000.0);
// String speedStr = formatTransferSpeed(speed);
//
// log.debug("Upload progress: {}% ({}/{} bytes, {})",
// progress, bytesWritten, totalBytes, speedStr);
// lastProgress = progress;
// }
//
// return true; // 返回true继续传输返回false中断传输
// }
//
// @Override
// public void end() {
// long elapsedTime = System.currentTimeMillis() - startTime;
// double speed = totalBytes / (elapsedTime / 1000.0);
// String speedStr = formatTransferSpeed(speed);
//
// log.info("Upload completed: {} bytes in {} ms (avg speed: {})",
// totalBytes, elapsedTime, speedStr);
// }
//
// @Override
// public void init(int op, String src, String dest, long max) {
// log.info("Starting upload: {} -> {} ({} bytes)", src, dest, max);
// }
//
// // 格式化传输速度
// private String formatTransferSpeed(double bytesPerSecond) {
// String[] units = {"B/s", "KB/s", "MB/s", "GB/s"};
// int unitIndex = 0;
//
// while (bytesPerSecond >= 1024 && unitIndex < units.length - 1) {
// bytesPerSecond /= 1024;
// unitIndex++;
// }
//
// return String.format("%.2f %s", bytesPerSecond, units[unitIndex]);
// }
// }
//
//
// /**
// * 检查连接状态
// */
// public ConnectionStatus getStatus() {
// if (status == ConnectionStatus.CONNECTED &&
// (sshSession == null || !sshSession.isConnected())) {
// status = ConnectionStatus.DISCONNECTED;
// }
// return status;
// }
//
// /**
// * 检查是否已连接
// */
// public boolean isConnected() {
// return status == ConnectionStatus.CONNECTED &&
// sshSession != null &&
// sshSession.isConnected();
// }
//
//
// private String getPrivateKeyContent(Long secretKeyId) {
// if (secretKeyId == null) {
// return null;
// }
// SecretKey secretKey = secretKeyService.getById(secretKeyId);
// byte[] content;
// try {
// content = aliYunOssClient.getContent(secretKey.getPath().substring(secretKey.getPath().lastIndexOf("/") + 1));
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
// //改为S3FileClient读取
// InputStream read = new ByteArrayInputStream(content);
//
// try {
// return StreamUtils.copyToString(read, StandardCharsets.UTF_8);
// } catch (IOException e) {
// log.error("读取私钥文件失败", e);
// throw new RuntimeException(e);
// }
//
//
// }
//}

View File

@ -0,0 +1,44 @@
package cd.casic.module.machine.service;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.controller.vo.MachineEnvVO;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import jakarta.validation.Valid;
/**
* 环境变量服务接口
*/
public interface MachineEnvService {
/**
* 创建或更新机器的环境变量一对一关系
*/
Long createEnv(@Valid MachineEnvVO machineEnvVO);
/**
* 删除机器的环境变量
*/
void deleteEnv(Long machineEvnId);
/**
* 获取机器的环境变量
*
* @param machineId 机器ID
* @return 环境变量DTO
*/
MachineEnvVO getEnv(Long machineId);
/**
* @return 环境变量列表
*/
PageResult<MachineEnvDO> getEnvPage(@Valid MachineEnvVO machineEnvVO);
/**
* 批量删除
*/
void deleteEnvList(String ids);
/*
* 修改环境变量
*/
void updateEnv(@Valid MachineEnvVO machineEnvVO);
}

View File

@ -0,0 +1,94 @@
package cd.casic.module.machine.service;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.controller.vo.MachineInfoDto;
import cd.casic.module.machine.dal.dataobject.MachineInfo;
import cd.casic.module.machine.enums.ConnectionStatus;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Map;
public interface MachineInfoService {
boolean addMachineInfo(MachineInfoDto MachineInfoDto);
PageResult<MachineInfoDto> listMachineInfo(MachineInfoDto MachineInfoDto);
boolean updateMachineInfo(MachineInfoDto machineInfoDto);
boolean updateStatus(MachineInfoDto machineInfoDto);
boolean bindingSecretKey(MachineInfoDto machineInfoDto);
void deleteList(String machineInfoIds);
void deleteMachineInfo(Long machineInfoId);
/**
* 测试机器连接
*
* @param id 机器id
* @return 连接是否成功
*/
boolean testConnection(Long id);
/**
* 获取机器连接状态
*
* @param machineName 机器名称
* @return 连接状态
*/
ConnectionStatus getConnectionStatus(String machineName);
/**
* 获取所有连接状态
*
* @return 机器名称到连接状态的映射
*/
Map<String, ConnectionStatus> getAllConnectionStatus();
/**
* 建立机器连接
*
* @param machineInfo 机器信息
* @return 连接会话ID
*/
String connect(MachineInfo machineInfo);
/**
* 断开机器连接
*
* @param sessionId 会话ID
* @return 操作结果
*/
boolean disconnect(String sessionId);
/**
* 执行远程命令
*
* @param sessionId 会话ID
* @param command 命令
* @return 命令执行结果
*/
String executeCommand(String sessionId, String command);
/**
* 上传文件到远程机器
*
* @param sessionId 会话ID
* @param localFilePath 本地文件路径
* @param remoteFilePath 远程文件路径
* @return 操作结果
*/
boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath);
/**
* 从远程机器下载文件
*
* @param sessionId 会话ID
* @param remoteFilePath 远程文件路径
* @param localFilePath 本地文件路径
* @return 操作结果
*/
boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath);
}

View File

@ -0,0 +1,41 @@
package cd.casic.module.machine.service;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.controller.vo.MachineProxyVO;
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
import java.util.Map;
/**
* 机器代理服务接口
*/
public interface MachineProxyService {
/**
* 注册新的机器代理
*/
Long createProxy(MachineProxyVO machineProxyVO);
/**
* 更新代理状态
*/
void updateProxy(MachineProxyVO machineProxyVO);
/**
* 获取所有代理的状态统计
*
* @return 状态统计Map
*/
Map<Integer, Long> getAllProxyStatus();
/**
* 批量删除代理
*
* @param proxyIds 代理ID列表
*/
void deleteProxyList(String proxyIds);
PageResult<MachineProxyDO> getProxyPage(MachineProxyVO machineProxyVO);
}

View File

@ -0,0 +1,24 @@
package cd.casic.module.machine.service;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.dal.dataobject.SecretKey;
import cd.casic.module.machine.controller.vo.SecretKeyDto;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface SecretKeyService extends IService<SecretKey> {
boolean addSecretKey(SecretKeyDto secretKeyDto) throws Exception;
void bindingMachine(Long secretKeyId, List<Long> machineInfoIds);
boolean updateSecretKey(SecretKeyDto secretKeyDto);
PageResult<SecretKey> listSecretKey(SecretKeyDto secretKeyDto);
boolean deleteList(List<Long> secretKeyIds);
}

View File

@ -0,0 +1,108 @@
package cd.casic.module.machine.service.impl;
import cd.casic.module.machine.convert.MachineEnvConvert;
import cd.casic.module.machine.controller.vo.MachineEnvVO;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import cd.casic.module.machine.dal.mysql.MachineEnvMapper;
import cd.casic.module.machine.service.MachineEnvService;
import cd.casic.framework.commons.pojo.PageResult;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cd.casic.framework.commons.util.object.BeanUtils;
import java.util.Arrays;
import java.util.List;
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
/**
* 环境变量服务实现类
*/
@Service("machineEnvService")
public class MachineEnvServiceImpl implements MachineEnvService {
@Resource
private MachineEnvMapper machineEnvMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createEnv(MachineEnvVO machineEnvVO) {
validateMachineEnvAdd(machineEnvVO);
// 检查键是否合法
validateKey(machineEnvVO.getEnvKey());
MachineEnvDO machineEnvDO = BeanUtils.toBean(machineEnvVO, MachineEnvDO.class);
machineEnvMapper.insert(machineEnvDO);
return machineEnvDO.getId();
}
@Override
public void deleteEnv(Long machineEvnId) {
machineEnvMapper.deleteById(machineEvnId);
}
@Override
public MachineEnvVO getEnv(Long machineId) {
if (machineId == null) {
return null;
}
MachineEnvDO machineEnvDO = machineEnvMapper.selectById(machineId);
return machineEnvDO != null ? MachineEnvConvert.INSTANCE.convertToVO(machineEnvDO) : null;
}
@Override
public PageResult<MachineEnvDO> getEnvPage(MachineEnvVO machineEnvVO) {
return machineEnvMapper.selectPage(machineEnvVO);
}
@Override
public void deleteEnvList(String ids) {
//ids转换为List,使用流
List<Long> machineEnvIds = Arrays.stream(ids.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Long::parseLong)
.toList();
machineEnvMapper.deleteBatchIds(machineEnvIds);
}
@Override
public void updateEnv(MachineEnvVO machineEnvVO) {
MachineEnvDO machineEnvDO = validateMachineEnvExists(machineEnvVO.getId());
BeanUtils.copyProperties(machineEnvVO, machineEnvDO);
machineEnvMapper.updateById(machineEnvDO);
}
@VisibleForTesting
MachineEnvDO validateMachineEnvExists(Long id) {
if (id == null) {
return null;
}
MachineEnvDO machineEnvDO = machineEnvMapper.selectById(id);
if (machineEnvDO == null) {
throw exception(MACHINE_ENV_NOT_EXISTS);
}
return machineEnvDO;
}
@VisibleForTesting
void validateMachineEnvAdd(MachineEnvVO machineEnvVO) {
if (machineEnvVO.getEnvKey()==null||machineEnvVO.getEnvValue()==null) {
throw exception(MACHINE_ENV_NULL);
}
}
// 检查环境变量键是否合法
@VisibleForTesting
private void validateKey(String key) {
if (!key.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
throw exception(MACHINE_ENV_KEY_ILLEGAL);
}
}
}

View File

@ -0,0 +1,138 @@
package cd.casic.module.machine.service.impl;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.controller.vo.MachineProxyVO;
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
import cd.casic.module.machine.enums.MachineProxyStatus;
import cd.casic.module.machine.enums.MachineProxyType;
import cd.casic.module.machine.dal.mysql.MachineProxyMapper;
import cd.casic.module.machine.service.MachineProxyService;
import cd.casic.module.machine.utils.EnumUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import cd.casic.framework.commons.util.object.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
import static com.baomidou.mybatisplus.extension.toolkit.Db.save;
import static org.apache.catalina.security.SecurityUtil.remove;
/**
* 机器代理服务实现类
*/
@Service
public class MachineProxyServiceImpl implements MachineProxyService {
@Resource
private MachineProxyMapper machineProxyMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createProxy(MachineProxyVO machineProxyVO) {
validateMachineProxyAdd(machineProxyVO);
// 创建代理记录
MachineProxyDO machineProxyDO = BeanUtils.toBean(machineProxyVO, MachineProxyDO.class);;
save(machineProxyDO);
return machineProxyDO.getId();
}
@Override
public void updateProxy(MachineProxyVO machineProxyVO) {
// 参数校验
MachineProxyDO machineProxyDO = validateMachineProxyExists(machineProxyVO.getId());
// 更新状态
BeanUtils.copyProperties(machineProxyVO, machineProxyDO);
machineProxyMapper.updateById(machineProxyDO);
}
@Override
public Map<Integer, Long> getAllProxyStatus() {
List<MachineProxyDO> proxyList = machineProxyMapper.selectList(new QueryWrapper<>());
if (CollectionUtils.isEmpty(proxyList)) {
return Collections.emptyMap();
}
return proxyList.stream()
.map(MachineProxyDO::getStatus)
.collect(Collectors.groupingBy(
Function.identity(),
// 统计每个分组的元素数量
Collectors.counting()
));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteProxyList(String ids) {
if (ids == null) {
return;
}
List<Long> machineProxyIds = Arrays.stream(ids.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Long::parseLong)
.toList();
//检查是否存在在线的代理
validateMachineProxyOnline(machineProxyIds);
// 批量逻辑删除
machineProxyMapper.deleteBatchIds(machineProxyIds);
}
@Override
public PageResult<MachineProxyDO> getProxyPage(MachineProxyVO machineProxyVO) {
return machineProxyMapper.selectPage(machineProxyVO);
}
@VisibleForTesting
MachineProxyDO validateMachineProxyExists(Long id) {
if (id == null) {
return null;
}
MachineProxyDO machineProxyDO = machineProxyMapper.selectById(id);
if (machineProxyDO == null) {
throw exception(MACHINE_PROXY_NOT_EXISTS);
}
return machineProxyDO;
}
@VisibleForTesting
void validateMachineProxyOnline(List<Long> ids) {
List<MachineProxyDO> machineProxyDOS = machineProxyMapper.selectBatchIds(ids);
List<Long>onlineId=new ArrayList<>();
machineProxyDOS.forEach(machineProxyDO->{
if (machineProxyDO.getStatus() == MachineProxyStatus.ONLINE.getCode()) {
onlineId.add(machineProxyDO.getId());
}
});
if(!onlineId.isEmpty()){
throw exception(MACHINE_PROXY_IS_ONLINE,onlineId);
}
}
@VisibleForTesting
void validateMachineProxyAdd(MachineProxyVO machineProxyVO) {
if (machineProxyVO.getHostIp()==null) {
throw exception(MACHINE_PROXY_HOST_IP_NULL);
}
if (machineProxyVO.getUsername()==null) {
throw exception(MACHINE_PROXY_USER_NAME_NULL);
}
// 校验代理类型
int[] arrays = MachineProxyType.ARRAYS;
if (Arrays.stream(arrays).filter(i -> i == machineProxyVO.getProxyType()).findAny().isEmpty()) {
throw exception(MACHINE_PROXY_TYPE_NOT_EXISTS);
}
}
}

View File

@ -0,0 +1,354 @@
//package cd.casic.module.machine.service.impl;
//import cd.casic.module.machine.enums.MachineInfoType;
//import cd.casic.module.machine.handler.ConnectionSession;
//import cd.casic.module.machine.dal.mysql.MachineInfoMapper;
//import cd.casic.module.machine.controller.vo.MachineInfoDto;
//import cd.casic.module.machine.dal.dataobject.MachineInfo;
//import cd.casic.module.machine.enums.AuthenticationType;
//import cd.casic.module.machine.enums.ConnectionStatus;
//import cd.casic.module.machine.enums.MachineInfoStatus;
//import cd.casic.module.machine.service.MachineInfoService;
//import cd.casic.module.machine.utils.EnumUtils;
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
//import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//import jakarta.annotation.Resource;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.beans.BeanUtils;
//import org.springframework.stereotype.Service;
//import java.util.Arrays;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//import java.util.concurrent.ConcurrentHashMap;
//import java.util.concurrent.atomic.AtomicInteger;
//import java.util.stream.Collectors;
//
//@Slf4j
//@Service("machineInfoService")
//public class MachineinfoServiceImpl implements MachineInfoService {
//
// int ENABLE = 1;
// int UN_ENABLE = 0;
//
// @Resource
// private MachineInfoMapper machineInfoMapper;
// @Resource
// private ConnectionSession connectionSession;
// /**
// * 会话ID生成器
// */
// private final AtomicInteger sessionIdGenerator = new AtomicInteger(1000);
//
// /**
// * 会话管理会话ID -> 连接会话
// */
// private final Map<String, ConnectionSession> sessions = new ConcurrentHashMap<>();
//
// /**
// * 机器名称 -> 会话ID
// */
// private final Map<String, String> machineSessionMapping = new ConcurrentHashMap<>();
//
// @Override
// public boolean addMachineInfo(MachineInfoDto machineInfoDto) {
// if (machineInfoDto == null) {
// throw new ServiceException(ServiceException.MACHINE_INFO_NULL, "机器信息为空");
// }
// MachineInfo machineInfo = new MachineInfo();
// BeanUtils.copyProperties(machineInfoDto, machineInfo);
// machineInfo.setStatusCode(1);
// machineInfo.setAuthenticationTypeCode(
// "密码认证".equals(machineInfoDto.getAuthenticationType())
// ? AuthenticationType.PASSWORD.getCode()
// : AuthenticationType.SECRET_KEY.getCode()
// );
// machineInfo.setMachineInfoTypeCode(
// "Windows".equals(machineInfoDto.getMachineInfoType())
// ? MachineInfoType.WINDOWS.getCode()
// : MachineInfoType.Linux.getCode()
// );
//
// return this.save(machineInfo);
// }
//
// @Override
// public PageResult<MachineInfoDto> listMachineInfo(MachineInfoDto machineInfoDto) {
// QueryWrapper<MachineInfo> queryWrapper = getMachineInfoQueryWrapper(machineInfoDto);
// Page<MachineInfo> page = machineInfoMapper.selectPage(
// new Page<>(machineInfoDto.getPageIndex(), machineInfoDto.getPageSize()),
// queryWrapper
// );
//
// List<MachineInfoDto> machineInfoDtos = page.getRecords().stream()
// .map(machineInfo -> {
// MachineInfoDto dto = new MachineInfoDto();
// BeanUtils.copyProperties(machineInfo, dto);
// // 直接调用原有枚举转换方法
// dto.setMachineInfoType(EnumUtils.getEnumByCode(machineInfo.getMachineInfoTypeCode(), MachineInfoType.class).getMessage());
// dto.setStatus(EnumUtils.getEnumByCode(machineInfo.getStatusCode(), MachineInfoStatus.class).getMessage());
// dto.setAuthenticationType(EnumUtils.getEnumByCode(machineInfo.getAuthenticationTypeCode(), AuthenticationType.class).getMessage());
// return dto;
// })
// .toList();
//
// return new PageResult<>(
// page.getCurrent(),
// page.getSize(),
// page.getTotal(),
// page.getPages(),
// machineInfoDtos
// );
// }
//
// @Override
// public boolean updateMachineInfo(MachineInfoDto machineInfoDto) {
// MachineInfo machineInfo = new MachineInfo();
// BeanUtils.copyProperties(machineInfoDto, machineInfo);
// machineInfo.setAuthenticationTypeCode(
// "密码认证".equals(machineInfoDto.getAuthenticationType())
// ? AuthenticationType.PASSWORD.getCode()
// : AuthenticationType.SECRET_KEY.getCode()
// );
// machineInfo.setMachineInfoTypeCode(
// "Windows".equals(machineInfoDto.getMachineInfoType())
// ? MachineInfoType.WINDOWS.getCode()
// : MachineInfoType.Linux.getCode()
// );
// return this.updateById(machineInfo);
// }
//
// @Override
// public boolean updateStatus(MachineInfoDto machineInfoDto) {
// UpdateWrapper<MachineInfo> updateWrapper = new UpdateWrapper<>();
// updateWrapper.eq("id", machineInfoDto.getId()).set("status_code", EnumUtils.getEnumByMessage(machineInfoDto.getStatus(), MachineInfoStatus.class).getCode());
// return this.update(updateWrapper);
// }
//
// @Override
// public boolean bindingSecretKey(MachineInfoDto machineInfoDto) {
// UpdateWrapper<MachineInfo> updateWrapper = new UpdateWrapper<>();
// updateWrapper.eq("id", machineInfoDto.getId()).set("secret_key_id", machineInfoDto.getSecretKeyId()).set("authentication_type_code", 2);
// return this.update(updateWrapper);
// }
//
// @Override
// public void deleteList(String machineInfoIds) {
// List<Long> machineInfoIdList = Arrays.stream(machineInfoIds.split(","))
// .map(String::trim)
// .filter(s -> !s.isEmpty())
// .map(Long::parseLong)
// .collect(Collectors.toList());
// machineInfoMapper.selectBatchIds(machineInfoIdList).forEach(machineInfo -> {
// if (machineInfo.getStatusCode() == 1) {
// this.removeById(machineInfo.getId());
// }
// });
// }
//
// @Override
// public void deleteMachineInfo(Long machineInfoId) {
// MachineInfo machineInfo = this.getById(machineInfoId);
// if (machineInfo.getStatusCode() == 1) {
// this.removeById(machineInfoId);
// }
// }
//
// private QueryWrapper<MachineInfo> getMachineInfoQueryWrapper(MachineInfoDto machineInfoDto) {
// QueryWrapper<MachineInfo> queryWrapper = new QueryWrapper<>();
// if (machineInfoDto.getStatus() != null && !machineInfoDto.getStatus().isEmpty()) {
// queryWrapper.eq("status_code", EnumUtils.getEnumByMessage(machineInfoDto.getStatus(), MachineInfoStatus.class).getCode());
// }
// if (machineInfoDto.getName() != null && !machineInfoDto.getName().isEmpty()) {
// queryWrapper.like("name", machineInfoDto.getName());
// }
// if (machineInfoDto.getTag() != null && !machineInfoDto.getTag().isEmpty()) {
// queryWrapper.like("tag", machineInfoDto.getTag());
// }
// if (machineInfoDto.getHostIp() != null && !machineInfoDto.getHostIp().isEmpty()) {
// queryWrapper.like("host_ip", machineInfoDto.getHostIp());
// }
// if (machineInfoDto.getDescription() != null && !machineInfoDto.getDescription().isEmpty()) {
// queryWrapper.like("description", machineInfoDto.getDescription());
// }
// return queryWrapper.orderByDesc("create_date");
// }
//
//
// @Override
// public boolean testConnection(Long id) {
// //先查询机器是否存在在判断机器可用性
// MachineInfo machineInfo = machineInfoMapper.getById(id);
// if (machineInfo==null){
// throw new RuntimeException("机器不存在");
// }
// if (machineInfo.getStatusCode() == 0) {
// throw new RuntimeException("机器不可用");
// }
// log.info("测试机器连接: {}", machineInfo.getHostIp());
// connectionSession.setMachineInfo(machineInfo);
// try{
// connectionSession.connect();
// return true;
// } catch (Exception e) {
// log.error("机器连接测试失败: {}", e.getMessage(), e);
// return false;
// }
// }
//
// @Override
// public ConnectionStatus getConnectionStatus(String machineName) {
// String sessionId = machineSessionMapping.get(machineName);
// if (sessionId == null) {
// return ConnectionStatus.DISCONNECTED;
// }
//
// ConnectionSession session = sessions.get(sessionId);
// return session != null ? session.getStatus() : ConnectionStatus.DISCONNECTED;
// }
//
// @Override
// public Map<String, ConnectionStatus> getAllConnectionStatus() {
// Map<String, ConnectionStatus> result = new HashMap<>();
//
// machineSessionMapping.forEach((machineName, sessionId) -> {
// ConnectionSession session = sessions.get(sessionId);
// result.put(machineName, session != null ? session.getStatus() : ConnectionStatus.DISCONNECTED);
// });
//
// return result;
// }
//
// @Override
// public String connect(MachineInfo machineInfo) {
// if (machineInfo.getStatus().getCode() == UN_ENABLE) {
// throw new RuntimeException("机器不可用");
// }
// log.info("建立机器连接: {}", machineInfo.getHostIp());
//
// // 检查是否已连接
// String existingSessionId = machineSessionMapping.get(machineInfo.getName());
// if (existingSessionId != null) {
// ConnectionSession existingSession = sessions.get(existingSessionId);
// if (existingSession != null && existingSession.getStatus() == ConnectionStatus.CONNECTED) {
// log.info("机器已连接,返回现有会话: {}", machineInfo.getHostIp());
// return existingSessionId;
// }
// }
//
// try {
// connectionSession.setMachineInfo(machineInfo);
//
// connectionSession.connect();
//
// // 生成会话ID
// String sessionId = generateSessionId();
//
// // 保存会话
// sessions.put(sessionId, connectionSession);
// machineSessionMapping.put(machineInfo.getName(), sessionId);
//
// log.info("机器连接成功: {}, 会话ID: {}", machineInfo.getHostIp(), sessionId);
// return sessionId;
// } catch (Exception e) {
// log.error("机器连接失败: {}", e.getMessage(), e);
// throw new RuntimeException("机器连接失败: " + e.getMessage(), e);
// }
// }
//
// @Override
// public boolean disconnect(String sessionId) {
// log.info("断开机器连接: {}", sessionId);
//
// ConnectionSession session = sessions.get(sessionId);
// if (session == null) {
// log.warn("会话不存在: {}", sessionId);
// return false;
// }
//
// try {
// session.disconnect();
//
// // 清理会话
// sessions.remove(sessionId);
// machineSessionMapping.entrySet().removeIf(entry -> entry.getValue().equals(sessionId));
//
// log.info("机器连接已断开: {}", sessionId);
// return true;
// } catch (Exception e) {
// log.error("断开连接失败: {}", e.getMessage(), e);
// return false;
// }
// }
//
// @Override
// public String executeCommand(String sessionId, String command) {
// log.info("执行命令: {}, 会话ID: {}", command, sessionId);
//
// ConnectionSession session = sessions.get(sessionId);
// if (session == null) {
// throw new RuntimeException("会话不存在: " + sessionId);
// }
//
// if (session.getStatus() != ConnectionStatus.CONNECTED) {
// throw new RuntimeException("会话未连接: " + sessionId);
// }
//
// try {
// return session.executeCommand(command);
// } catch (Exception e) {
// log.error("命令执行失败: {}", e.getMessage(), e);
// throw new RuntimeException("命令执行失败: " + e.getMessage(), e);
// }
// }
//
// @Override
// public boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath) {
// log.info("上传文件: {} -> {}, 会话ID: {}", localFilePath, remoteFilePath, sessionId);
//
// ConnectionSession session = sessions.get(sessionId);
// if (session == null) {
// throw new RuntimeException("会话不存在: " + sessionId);
// }
//
// if (session.getStatus() != ConnectionStatus.CONNECTED) {
// throw new RuntimeException("会话未连接: " + sessionId);
// }
//
// try {
// return session.uploadFile(localFilePath, remoteFilePath);
// } catch (Exception e) {
// log.error("文件上传失败: {}", e.getMessage(), e);
// throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
// }
// }
//
// @Override
// public boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath) {
// log.info("下载文件: {} -> {}, 会话ID: {}", remoteFilePath, localFilePath, sessionId);
//
// ConnectionSession session = sessions.get(sessionId);
// if (session == null) {
// throw new RuntimeException("会话不存在: " + sessionId);
// }
//
// if (session.getStatus() != ConnectionStatus.CONNECTED) {
// throw new RuntimeException("会话未连接: " + sessionId);
// }
//
// try {
// return session.downloadFile(remoteFilePath, localFilePath);
// } catch (Exception e) {
// log.error("文件下载失败: {}", e.getMessage(), e);
// throw new RuntimeException("文件下载失败: " + e.getMessage(), e);
// }
// }
//
//
// /**
// * 生成会话ID
// */
// private String generateSessionId() {
// return "session-" + sessionIdGenerator.incrementAndGet();
// }
//}

View File

@ -0,0 +1,159 @@
//package cd.casic.module.machine.service.impl;
//
//import cd.casic.module.machine.controller.vo.SecretKeyDto;
//import cd.casic.module.machine.dal.dataobject.MachineInfo;
//import cd.casic.module.machine.dal.dataobject.SecretKey;
//import cd.casic.module.machine.dal.mysql.SecretServiceMapper;
//import cd.casic.module.machine.service.MachineInfoService;
//import cn.hutool.core.io.resource.ResourceUtil;
//import cn.hutool.core.util.IdUtil;
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
//
//import cd.casic.module.machine.utils.AliYunOssClient;
//import cd.casic.module.machine.exception.ServiceException;
////import cd.casic.module.machine.service.MachineInfoService;
//import cd.casic.module.machine.service.SecretKeyService;
//import jakarta.annotation.Resource;
//import org.springframework.beans.BeanUtils;
//import org.springframework.stereotype.Service;
//
//import java.util.List;
//
//@Service
//public class SecretKeyServiceImpl extends ServiceImpl<SecretServiceMapper, SecretKey> implements SecretKeyService {
//
// @Resource
// private MachineInfoService machineInfoService;
//
// @Resource
// private AliYunOssClient aliYunOssClient;
//
// @Resource
// private SecretServiceMapper secretServiceMapper;
//
//
//
//
// @Override
// public boolean addSecretKey(SecretKeyDto secretKeyDto){
// if (secretKeyDto.getPath()==null)
// {
// throw new ServiceException(ServiceException.MACHINE_PROXY_NULL,"密钥不能为空");
// }
//
// String ossPath = upLoadSecretKey(secretKeyDto.getPath());
// if (ossPath == null){
// throw new ServiceException(ServiceException.MACHINE_PROXY_NULL,"密钥上传失败");
// }
// secretKeyDto.setPath(ossPath);
// SecretKey secretKey = new SecretKey();
// BeanUtils.copyProperties(secretKeyDto,secretKey);
// //todo检查密钥合法
// return this.save(secretKey);
//
//
// }
// @Override
// public boolean updateSecretKey(SecretKeyDto secretKeyDto) {
//
// Long id = secretKeyDto.getId();
// SecretKey secretKey = this.getById(id);
// if (secretKey == null){
// throw new ServiceException(ServiceException.MACHINE_PROXY_NULL,"密钥不存在");
// }
// if (!secretKey.getPath().equals(secretKeyDto.getPath())) {
// //todo检查密钥合法
// String ossPath = upLoadSecretKey(secretKeyDto.getPath());
// BeanUtils.copyProperties(secretKeyDto,secretKey);
// secretKey.setPath(ossPath);
// }
// else {
// BeanUtils.copyProperties(secretKeyDto,secretKey);
// }
//
// return this.updateById(secretKey);
//
//
// }
//
// @Override
// public void bindingMachine(Long secretKeyId, List<Long> machineIds) {
// SecretKey secretKey = this.getById(secretKeyId);
// if (secretKey==null){
// throw new ServiceException(ServiceException.SECRETKEY_NULL,"密钥不存在");
// }
// List<MachineInfo> machineList = machineInfoService.listByIds(machineIds);
// machineList.forEach(machine -> machine.setSecretKeyId(secretKeyId));
// machineInfoService.updateBatchById(machineList);
// }
//
//
//
//
//
// @Override
// public PageResult<SecretKey> listSecretKey(SecretKeyDto secretKeyDto) {
// QueryWrapper<SecretKey> queryWrapper = new QueryWrapper<>();
// if (secretKeyDto.getName() != null && !secretKeyDto.getName().isEmpty()){
// queryWrapper.like("name", secretKeyDto.getName());
// }
// if (secretKeyDto.getDescription() != null && !secretKeyDto.getDescription().isEmpty()){
// queryWrapper.like("description", secretKeyDto.getDescription());
// }
// Page<SecretKey> page = secretServiceMapper.selectPage(new Page<>(secretKeyDto.getPageIndex(), secretKeyDto.getPageSize()), queryWrapper);
// return new PageResult<>(
// page.getCurrent(),
// page.getSize(),
// page.getTotal(),
// page.getPages(),
// page.getRecords()
// );
// }
//
//
//
// @Override
// public boolean deleteList(List<Long> secretKeyIds) {
// List<SecretKey> secretKeys = this.listByIds(secretKeyIds);
//
// for (SecretKey secretKey : secretKeys) {
// if (secretKey.getPath() != null && !secretKey.getPath().isEmpty()){
// try {
// //文件名
// //删除子目录文件需要在前面加上根目录文件路径
// String fileName = secretKey.getPath().substring(secretKey.getPath().lastIndexOf("/") + 1);
// aliYunOssClient.delete(fileName);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
// }
//
//
// //todo是否删除已经绑定的机器
// return secretServiceMapper.deleteBatchIds(secretKeyIds) > 0 ;
// }
//
//
// public String upLoadSecretKey(String localPath) {
//
// //使用S3FileClient上传文件
// aliYunOssClient.init();
//
// //传输到指定文件需要在path前面加上文件路径
// String path = IdUtil.fastSimpleUUID() + ".txt";
//
//
// //上传文件是从本地上传这里传的是本地文件地址
// byte[] content = ResourceUtil.readBytes(localPath);
// String ossPath;
// try {
// ossPath = aliYunOssClient.upload(content, path, "txt");
// }catch (Exception e) {
// throw new RuntimeException(e+"上传文件失败");
// }
// return ossPath;
// }
//}

View File

@ -0,0 +1,12 @@
package cd.casic.module.machine.utils;
import cd.casic.module.infra.framework.file.core.client.s3.S3FileClient;
import cd.casic.module.infra.framework.file.core.client.s3.S3FileClientConfig;
public class AliYunOssClient extends S3FileClient{
public AliYunOssClient(Long id, S3FileClientConfig config) {
super(id, config);
}
}

View File

@ -0,0 +1,50 @@
package cd.casic.module.machine.utils;
import cd.casic.module.machine.enums.CodeEnum;
public class EnumUtils {
/**
* 根据code和枚举类型获取对应的枚举值
*
* @param code 枚举的code值
* @param enumClass 实现了CodeEnum接口的枚举类
* @param <T> 枚举类型
* @return 对应的枚举值若未找到则返回null
*/
public static <T extends CodeEnum> T getEnumByCode(Object code, Class<T> enumClass) {
if (code == null || enumClass == null) {
return null;
}
// 遍历枚举值查找匹配的code
for (T enumConstant : enumClass.getEnumConstants()) {
if (code.equals(enumConstant.getCode())) {
return enumConstant;
}
}
return null;
}
/**
* 根据message和枚举类型获取对应的枚举值
*
* @param message 枚举的message值
* @param enumClass 实现了CodeEnum接口的枚举类
* @param <T> 枚举类型
* @return 对应的枚举值若未找到则返回null
*/
public static <T extends CodeEnum> T getEnumByMessage(String message, Class<T> enumClass) {
if (message == null || enumClass == null) {
return null;
}
// 遍历枚举值查找匹配的message
for (T enumConstant : enumClass.getEnumConstants()) {
if (message.equals(enumConstant.getMessage())) {
return enumConstant;
}
}
return null;
}
}

View File

@ -0,0 +1,35 @@
package cd.casic.module.machine.utils;
import io.micrometer.common.util.StringUtils;
import java.util.function.Consumer;
/**
* 属性操作工具类
*/
public class PropertyUtils {
/**
* 如果不为空则设置值
*
* @param value
* @param setter 具体set操作
*/
public static void setIfNotBlank(String value, Consumer<String> setter) {
if (StringUtils.isNotBlank(value)) {
setter.accept(value);
}
}
/**
* 如果不为空则设置值
*
* @param value
* @param setter 具体set操作
*/
public static <T> void setIfNotNull(T value, Consumer<T> setter) {
if (value != null) {
setter.accept(value);
}
}
}

View File

@ -0,0 +1,78 @@
-- ----------------------------
-- Table structure for machine_env
-- ----------------------------
DROP TABLE IF EXISTS `machine_env`;
CREATE TABLE `machine_env` (
`id` bigint NOT NULL AUTO_INCREMENT,
`create_date` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
`machine_id` bigint NULL DEFAULT NULL,
`env_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`env_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`sensitive` tinyint(1) NULL DEFAULT NULL COMMENT '是否敏感0=false, 1=true, NULL=未设置)', -- 改为 tinyint 存储布尔值
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_machine_env_key` (`machine_id`, `env_key`) -- 同一机器下 env_key 唯一
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for machine_info
-- ----------------------------
DROP TABLE IF EXISTS `machine_info`;
CREATE TABLE `machine_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`host_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`status_code` int NULL DEFAULT NULL COMMENT '状态编码(关联字典表)', -- 改为 int 类型
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`ssh_port` int NULL DEFAULT NULL COMMENT 'SSH端口号整数', -- 改为 int 类型
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`secret_key_id` bigint NULL DEFAULT NULL COMMENT '密钥ID逻辑关联无外键',
`machine_proxy_id` bigint NULL DEFAULT NULL COMMENT '代理ID逻辑关联无外键',
`authentication_type_code` int NULL DEFAULT NULL COMMENT '认证类型编码',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_secret_key_id` (`secret_key_id`), -- 保留索引优化查询
KEY `idx_machine_proxy_id` (`machine_proxy_id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for machine_proxy
-- ----------------------------
DROP TABLE IF EXISTS `machine_proxy`;
CREATE TABLE `machine_proxy` (
`id` bigint NOT NULL AUTO_INCREMENT,
`create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
`host_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`ssh_port` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`proxy_type_code` tinyint(1) NULL DEFAULT NULL COMMENT '代理类型编码(关联字典表)',
`version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`status_code` tinyint(1) NULL DEFAULT NULL COMMENT '状态编码(关联字典表)', -- 改为 tinyint(1) 类型
`last_heartbeat_time` datetime(0) NULL DEFAULT NULL,
`config` json NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for machine_secret_key
-- ----------------------------
DROP TABLE IF EXISTS `machine_secret_key`;
CREATE TABLE `machine_secret_key` (
`id` bigint NOT NULL AUTO_INCREMENT,
`create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,184 @@
//package com.casic.machine.service.impl;
//
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
//import com.casic.commons.exception.ServiceException;
//import com.casic.commons.utils.PageResult;
//import com.casic.machine.dto.MachineEnvDTO;
//import com.casic.machine.entity.MachineEnv;
//import com.casic.machine.mapper.MachineEnvMapper;
//import com.casic.machine.service.MachineEnvService;
//import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.Test;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.test.context.SpringBootTest;
//import org.springframework.test.annotation.Rollback;
//import org.springframework.test.context.jdbc.Sql;
//import org.springframework.transaction.annotation.Transactional;
//
//import java.util.Date;
//import java.util.List;
//
//import static org.junit.jupiter.api.Assertions.*;
//
//@SpringBootTest
//@Transactional
//@Rollback(value = true) // 测试后回滚数据
//@Sql(scripts = {"classpath:sql/machine_env_test_data.sql"}) // 初始化测试数据可选
//public class MachineEnvServiceImplTest {
//
// @Autowired
// private MachineEnvService machineEnvService;
//
// @Autowired
// private MachineEnvMapper machineEnvMapper;
//
// private MachineEnvDTO validDto;
// private MachineEnvDTO invalidKeyDto;
// private Long existingMachineId;
// private Long nonExistingMachineId;
//
// @BeforeEach
// public void setUp() {
// // 准备测试数据
// existingMachineId = 1L; // 假设数据库中存在ID为1的机器环境变量
// nonExistingMachineId = 999L; // 不存在的机器ID
//
// // 有效测试数据
// validDto = new MachineEnvDTO();
// validDto.setMachineInfoId(existingMachineId);
// validDto.setEnvKey("TEST_ENV_KEY");
// validDto.setEnvValue("test-value");
// validDto.setSensitive(true);
//
// // 无效Key测试数据包含非法字符
// invalidKeyDto = new MachineEnvDTO();
// invalidKeyDto.setMachineInfoId(existingMachineId);
// invalidKeyDto.setEnvKey("test-env-key"); // 包含'-'不符合正则
// invalidKeyDto.setEnvValue("test-value");
// }
//
// // ==================== 更新环境变量测试 ====================
// @Test
// void testUpdateEnv_ValidData_ShouldSucceed() {
// // 执行更新假设数据库中已存在machineId=1的记录
// boolean result = machineEnvService.update(validDto);
//
// // 验证结果
// assertTrue(result);
//
// // 检查数据库数据是否更新
// MachineEnv updatedEnv = machineEnvMapper.selectOne(
// new LambdaQueryWrapper<MachineEnv>().eq(MachineEnv::getMachineId, existingMachineId)
// );
// assertNotNull(updatedEnv);
// assertEquals(validDto.getEnvKey(), updatedEnv.getEnvKey());
// assertEquals(validDto.getEnvValue(), updatedEnv.getEnvValue());
// assertEquals(validDto.getSensitive(), updatedEnv.getSensitive());
// }
//
// @Test
// void testUpdateEnv_NullDto_ShouldThrowException() {
//
// MachineEnvDTO machineEnvDTO = new MachineEnvDTO();
// assertThrows(ServiceException.class, () -> {
// machineEnvService.update(machineEnvDTO);
// }, "环境变量不能为空");
// }
//
// @Test
// void testUpdateEnv_InvalidKey_ShouldThrowException() {
// assertThrows(ServiceException.class, () -> {
// machineEnvService.update(invalidKeyDto);
// }, "环境变量键不合法");
// }
//
// @Test
// void testUpdateEnv_SensitiveKey_ShouldMarkAsSensitive() {
// MachineEnvDTO sensitiveDto = new MachineEnvDTO();
// sensitiveDto.setMachineInfoId(existingMachineId);
// sensitiveDto.setEnvKey("DB_PASSWORD"); // 包含敏感词
// sensitiveDto.setEnvValue("secret");
//
// machineEnvService.update(sensitiveDto);
//
// MachineEnv env = machineEnvMapper.selectOne(
// new LambdaQueryWrapper<MachineEnv>().eq(MachineEnv::getMachineId, existingMachineId)
// );
// assertTrue(env.getSensitive());
// }
//
// // ==================== 删除环境变量测试 ====================
// @Test
// void testDeleteByMachineId_ExistingId_ShouldSucceed() {
// machineEnvService.deleteByMachineId(existingMachineId);
// MachineEnv env = machineEnvMapper.selectById(existingMachineId);
// assertNull(env);
// }
//
// @Test
// void testDeleteByMachineId_NonExistingId_ShouldDoNothing() {
// machineEnvService.deleteByMachineId(nonExistingMachineId);
// // 不抛出异常静默处理
// }
//
// // ==================== 根据机器ID查询测试 ====================
// @Test
// void testGetByMachineId_ExistingId_ShouldReturnDto() {
// MachineEnvDTO dto = machineEnvService.getByMachineId(existingMachineId);
// assertNotNull(dto);
// assertEquals(existingMachineId, dto.getMachineInfoId());
// }
//
// @Test
// void testGetByMachineId_NonExistingId_ShouldReturnNull() {
// MachineEnvDTO dto = machineEnvService.getByMachineId(nonExistingMachineId);
// assertNull(dto);
// }
//
// // ==================== 列表查询测试 ====================
// @Test
// void testListEnv_Pagination_ShouldReturnValidPage() {
// MachineEnvDTO queryDto = new MachineEnvDTO();
// queryDto.setPageIndex(1);
// queryDto.setPageSize(10);
// queryDto.setEnvKey("TEST"); // 假设测试数据中存在包含"TEST"的键
//
// PageResult<MachineEnvDTO> pageResult = machineEnvService.listEnv(queryDto);
//
// assertNotNull(pageResult.getList());
// assertTrue(pageResult.getTotal() >= 0);
// assertEquals(1, pageResult.getPageNum());
// }
//
// @Test
// void testListEnv_SortByCreateTime_ShouldBeOrdered() {
// MachineEnvDTO queryDto = new MachineEnvDTO();
// queryDto.setSortField("createTime");
// queryDto.setSortDirection("desc");
//
// PageResult<MachineEnvDTO> pageResult = machineEnvService.listEnv(queryDto);
//
// List<MachineEnvDTO> list = pageResult.getList();
// if (!list.isEmpty()) {
// Date prevDate = list.get(0).getCreateDate();
// for (int i = 1; i < list.size(); i++) {
// Date currDate = list.get(i).getCreateDate();
// assertTrue(currDate.before(prevDate) || currDate.equals(prevDate), "排序应为降序");
// prevDate = currDate;
// }
// }
// }
//
// // ==================== 批量删除测试 ====================
// @Test
// void testDeleteList_ValidIds_ShouldDeleteBatch() {
// // 假设测试数据中有ID为1和2的记录
// List<Long> ids = List.of(1L, 2L);
// machineEnvService.deleteList(ids);
//
// long count = machineEnvMapper.selectCount(new LambdaQueryWrapper<MachineEnv>().in(MachineEnv::getId, ids));
// assertEquals(0, count);
// }
//
//
//}

View File

@ -0,0 +1,222 @@
//package com.casic.machine.service.impl;
//
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
//import com.casic.commons.exception.ServiceException;
//import com.casic.commons.utils.EnumUtils;
//import com.casic.commons.utils.PageResult;
//import com.casic.machine.dto.MachineProxyDTO;
//import com.casic.machine.entity.MachineProxy;
//import com.casic.machine.enums.MachineProxyStatus;
//import com.casic.machine.enums.MachineProxyType;
//import com.casic.machine.mapper.MachineProxyMapper;
//import com.casic.machine.service.MachineProxyService;
//import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.Test;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.test.context.SpringBootTest;
//import org.springframework.test.annotation.Rollback;
//import org.springframework.test.context.jdbc.Sql;
//import org.springframework.transaction.annotation.Transactional;
//
//import java.util.*;
//
//import static org.junit.jupiter.api.Assertions.*;
//
//@SpringBootTest
//@Transactional
//@Rollback(true)
//@Sql(scripts = {"classpath:sql/machine_proxy_test_data.sql"})
//public class MachineProxyServiceImplTest {
//
// @Autowired
// private MachineProxyService machineProxyService;
//
// @Autowired
// private MachineProxyMapper machineProxyMapper;
//
// private MachineProxyDTO validProxyDTO;
// private Long existingProxyId;
// private Long nonExistingProxyId;
//
// @BeforeEach
// public void setUp() {
// // 初始化测试数据假设 SQL 脚本已插入一条状态为 OFFLINE 的代理
// existingProxyId = 1L;
// nonExistingProxyId = 999L;
//
// // 有效代理 DTO
// validProxyDTO = new MachineProxyDTO();
// validProxyDTO.setProxyType(MachineProxyType.SOCKS5.getMessage());
// validProxyDTO.setHostIp("192.168.1.100");
// validProxyDTO.setSshPort("22");
// validProxyDTO.setUsername("test_user");
// validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
// }
//
// // ============================== 注册代理测试 ==============================
// @Test
// void testRegister_ValidData_ShouldSucceed() {
// // 执行注册
// boolean result = machineProxyService.register(validProxyDTO);
// assertTrue(result, "注册失败");
//
// // 使用 Lambda 表达式查询推荐
// MachineProxy proxy = machineProxyMapper.selectOne(
// new LambdaQueryWrapper<MachineProxy>()
// .eq(MachineProxy::getHostIp, validProxyDTO.getHostIp())
// );
//
// // 断言数据存在
// assertNotNull(proxy, "代理记录未写入数据库");
// assertEquals(MachineProxyType.SOCKS5.getCode(), proxy.getProxyTypeCode());
// assertEquals(MachineProxyStatus.INSTALLING.getCode(), proxy.getStatusCode());
// assertEquals(validProxyDTO.getHostIp(), proxy.getHostIp(), "IP 地址不一致");
// }
//
// // ============================== 更新状态测试 ==============================
// @Test
// void testUpdateStatus_ExistingProxy_ShouldUpdateStatus() {
// // 准备数据查询现有代理状态为 OFFLINE
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
// assertEquals(MachineProxyStatus.OFFLINE.getCode(), proxy.getStatusCode());
//
// // 执行状态更新为 ONLINE
// validProxyDTO.setId(existingProxyId);
// validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
// boolean result = machineProxyService.updateStatus(validProxyDTO);
//
// // 验证结果
// assertTrue(result);
// proxy = machineProxyMapper.selectById(existingProxyId);
// assertEquals(MachineProxyStatus.ONLINE.getCode(), proxy.getStatusCode());
// }
//
// @Test
// void testUpdateStatus_NullDto_ShouldThrowException() {
// assertThrows(ServiceException.class, () -> {
// machineProxyService.updateStatus(null);
// }, "MachineProxyDTO对象为空");
// }
//
// // ============================== 心跳测试 ==============================
// @Test
// void testHeartbeat_ValidData_ShouldUpdateVersionAndStatus() {
// // 准备数据现有代理状态为 OFFLINE版本为 1.0.0
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
// assertEquals("1.0.0", proxy.getVersion());
// assertEquals(MachineProxyStatus.OFFLINE.getCode(), proxy.getStatusCode());
//
// // 发送心跳更新版本和状态
// validProxyDTO.setId(existingProxyId);
// validProxyDTO.setVersion("2.0.0");
// validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
// machineProxyService.heartbeat(validProxyDTO);
//
// // 验证结果
// proxy = machineProxyMapper.selectById(existingProxyId);
// assertEquals("2.0.0", proxy.getVersion());
// assertEquals(MachineProxyStatus.ONLINE.getCode(), proxy.getStatusCode());
// }
//
// // ============================== 状态统计测试 ==============================
// @Test
// void testGetStatusStatistics_ShouldReturnValidCounts() {
// // 假设测试数据中有 OFFLINE(1)INSTALLING(1)ONLINE(1) 三种状态
// Map<String, Long> stats = machineProxyService.getStatusStatistics();
//
// // 验证统计结果
// assertEquals(3, stats.size());
// assertTrue(stats.containsKey(MachineProxyStatus.OFFLINE.getMessage()));
// assertTrue(stats.containsKey(MachineProxyStatus.INSTALLING.getMessage()));
// assertTrue(stats.containsKey(MachineProxyStatus.ONLINE.getMessage()));
// assertEquals(1L, stats.get(MachineProxyStatus.OFFLINE.getMessage()));
// }
//
// // ============================== 更新配置测试 ==============================
// @Test
// void testUpdateConfig_ValidConfig_ShouldSucceed() {
// // 准备数据现有代理配置为空
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
// assertNull(proxy.getConfig());
//
// // 更新配置
// validProxyDTO.setId(existingProxyId);
// validProxyDTO.setConfig("{\"port\": 8080}");
// boolean result = machineProxyService.updateConfig(validProxyDTO);
//
// // 验证结果
// assertTrue(result);
// proxy = machineProxyMapper.selectById(existingProxyId);
// assertEquals("{\"port\": 8080}", proxy.getConfig());
// }
//
// // ============================== 删除代理测试 ==============================
// @Test
// void testDelete_OfflineProxy_ShouldDeleteSuccessfully() {
// // 准备数据状态为 OFFLINE 的代理 ID
// List<Long> ids = Collections.singletonList(existingProxyId);
// machineProxyService.delete(ids);
//
// // 验证删除
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
// assertNull(proxy);
// }
//
// @Test
// void testDelete_OnlineProxy_ShouldThrowException() {
// // 先将代理状态改为 ONLINE
// MachineProxy onlineProxy = new MachineProxy();
// onlineProxy.setId(existingProxyId);
// onlineProxy.setStatusCode(MachineProxyStatus.ONLINE.getCode());
// machineProxyMapper.updateById(onlineProxy);
//
// // 执行删除
// List<Long> ids = Collections.singletonList(existingProxyId);
// assertThrows(IllegalArgumentException.class, () -> {
// machineProxyService.delete(ids);
// }, "以下代理处于在线状态,无法删除: 1");
// }
//
// // ============================== 列表查询测试 ==============================
// @Test
// void testList_WithStatusFilter_ShouldReturnMatchedRecords() {
// // 查询状态为 OFFLINE 的代理
// MachineProxyDTO queryDto = new MachineProxyDTO();
// queryDto.setStatus(MachineProxyStatus.OFFLINE.getMessage());
//
// PageResult<MachineProxyDTO> pageResult = machineProxyService.list(queryDto);
//
// // 验证结果
// assertFalse(pageResult.getList().isEmpty());
// pageResult.getList().forEach(dto ->
// assertEquals(MachineProxyStatus.OFFLINE.getMessage(), dto.getStatus())
// );
// }
//
// @Test
// void testList_WithHostIpFilter_ShouldReturnMatchedRecords() {
// // 假设测试数据中存在 host_ip "192.168.1.1" 的代理
// MachineProxyDTO queryDto = new MachineProxyDTO();
// queryDto.setHostIp("192.168.1.1");
//
// PageResult<MachineProxyDTO> pageResult = machineProxyService.list(queryDto);
//
// // 验证结果
// assertFalse(pageResult.getList().isEmpty());
// pageResult.getList().forEach(dto ->
// assertEquals("192.168.1.1", dto.getHostIp())
// );
// }
//
// // ============================== 辅助方法测试 ==============================
// @Test
// void testEnumUtils_ConvertCodeToMessage() {
// // 验证代理类型枚举转换
// String typeMessage = EnumUtils.getEnumByCode(MachineProxyType.HTTP.getCode(), MachineProxyType.class).getMessage();
// assertEquals("HTTP", typeMessage);
//
// // 验证状态枚举转换
// String statusMessage = EnumUtils.getEnumByCode(MachineProxyStatus.INSTALLING.getCode(), MachineProxyStatus.class).getMessage();
// assertEquals("安装中", statusMessage);
// }
//}

View File

@ -0,0 +1,176 @@
//package com.casic.machine.service.impl;
//
//import com.casic.commons.exception.ServiceException;
//import com.casic.machine.dto.MachineInfoDto;
//import com.casic.machine.entity.MachineInfo;
//import com.casic.machine.enums.AuthenticationType;
//import com.casic.machine.enums.ConnectionStatus;
//import com.casic.machine.enums.MachineInfoStatus;
//import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.Test;
//import org.springframework.beans.BeanUtils;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.test.context.SpringBootTest;
//import org.springframework.test.annotation.Rollback;
//import org.springframework.test.context.jdbc.Sql;
//import org.springframework.transaction.annotation.Transactional;
//
//import java.util.Collections;
//
//import static org.junit.jupiter.api.Assertions.*;
//
//@SpringBootTest
//@Transactional
//@Rollback(true)
//@Sql(scripts = {"classpath:sql/machine_info_test_data.sql"})
//public class MachineinfoServiceImplTest {
//
// @Autowired
// private MachineinfoServiceImpl machineInfoService;
//
// private MachineInfoDto validMachineInfoDto;
// private Long existingMachineId;
// private Long nonExistingMachineId;
//
// @BeforeEach
// public void setUp() {
// // 初始化测试数据假设 SQL 脚本已插入一条状态为 ENABLE 的机器
// existingMachineId = 1L;
// nonExistingMachineId = 999L;
//
// // 有效机器信息 DTO
// validMachineInfoDto = new MachineInfoDto();
// validMachineInfoDto.setName("Test Machine");
// validMachineInfoDto.setHostIp("192.168.1.101");
// validMachineInfoDto.setSshPort("22");
// validMachineInfoDto.setUsername("testuser");
// validMachineInfoDto.setAuthenticationType(AuthenticationType.PASSWORD.getMessage());
// validMachineInfoDto.setStatus(MachineInfoStatus.ENABLE.getMessage());
// }
//
// // ======================= 新增机器测试 =======================
// @Test
// void testAddMachineInfo_ValidData_ShouldSucceed() {
// boolean result = machineInfoService.addMachineInfo(validMachineInfoDto);
// assertTrue(result);
//
// // 验证数据库存在记录
// MachineInfo machineInfo = machineInfoService.getById(validMachineInfoDto.getId());
// assertNotNull(machineInfo);
// assertEquals(validMachineInfoDto.getHostIp(), machineInfo.getHostIp());
// assertEquals(AuthenticationType.PASSWORD.getCode(), machineInfo.getAuthenticationTypeCode());
// }
//
// @Test
// void testAddMachineInfo_NullDto_ShouldThrowException() {
// assertThrows(ServiceException.class, () -> {
// machineInfoService.addMachineInfo(null);
// }, "机器信息为空");
// }
//
// // ======================= 列表查询测试 =======================
// @Test
// void testListMachineInfo_WithStatusFilter_ShouldReturnValidRecords() {
// MachineInfoDto queryDto = new MachineInfoDto();
// queryDto.setStatus(MachineInfoStatus.ENABLE.getMessage());
//
// var pageResult = machineInfoService.listMachineInfo(queryDto);
// assertFalse(pageResult.getList().isEmpty());
// pageResult.getList().forEach(dto ->
// assertEquals(MachineInfoStatus.ENABLE.getMessage(), dto.getStatus())
// );
// }
//
// @Test
// void testListMachineInfo_WithHostIpFilter_ShouldFilterRecords() {
// MachineInfoDto queryDto = new MachineInfoDto();
// queryDto.setHostIp("192.168.1.100"); // 假设测试数据中的 IP
//
// var pageResult = machineInfoService.listMachineInfo(queryDto);
// assertFalse(pageResult.getList().isEmpty());
// pageResult.getList().forEach(dto ->
// assertTrue(dto.getHostIp().contains("192.168.1.100"))
// );
// }
//
// // ======================= 更新机器状态测试 =======================
// @Test
// void testUpdateStatus_ValidId_ShouldUpdateStatus() {
// boolean result = machineInfoService.updateStatus(existingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
// assertTrue(result);
//
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
// assertEquals(MachineInfoStatus.UN_ENABLE.getCode(), machineInfo.getStatus());
// }
//
// @Test
// void testUpdateStatus_NonExistingId_ShouldFail() {
// boolean result = machineInfoService.updateStatus(nonExistingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
// assertFalse(result);
// }
//
// // ======================= 连接测试 =======================
// @Test
// void testTestConnection_EnabledMachine_ShouldSucceed() {
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
// assertTrue(machineInfoService.testConnection(machineInfo));
// }
//
// @Test
// void testTestConnection_DisabledMachine_ShouldThrowException() {
// // 先禁用机器
// machineInfoService.updateStatus(existingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
// MachineInfo disabledMachine = machineInfoService.getById(existingMachineId);
//
// assertThrows(RuntimeException.class, () -> {
// machineInfoService.testConnection(disabledMachine);
// }, "机器不可用");
// }
//
// // ======================= 会话管理测试 =======================
// @Test
// void testConnect_NewMachine_ShouldCreateSession() {
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
// String sessionId = machineInfoService.connect(machineInfo);
//
// assertNotNull(sessionId);
// assertTrue(sessionId.startsWith("session-"));
// assertEquals(ConnectionStatus.CONNECTING, machineInfoService.getConnectionStatus(machineInfo.getName()));
// }
//
//
//
// // ======================= 删除机器测试 =======================
// @Test
// void testDeleteMachineInfo_EnabledMachine_ShouldDelete() {
// machineInfoService.deleteMachineInfo(existingMachineId);
// assertNull(machineInfoService.getById(existingMachineId));
// }
//
// @Test
// void testDeleteList_ValidIds_ShouldDeleteBatch() {
// machineInfoService.deleteList(Collections.singletonList(existingMachineId));
// var list = machineInfoService.list();
// assertFalse(list.contains(existingMachineId));
// }
//
// // ======================= 辅助功能测试 =======================
// @Test
// void testAuthenticationTypeConversion() {
// validMachineInfoDto.setAuthenticationType(AuthenticationType.SECRET_KEY.getMessage());
// MachineInfo machineInfo = new MachineInfo();
// BeanUtils.copyProperties(validMachineInfoDto, machineInfo);
//
// assertEquals(AuthenticationType.SECRET_KEY.getCode(), machineInfo.getAuthenticationTypeCode());
// }
//
// @Test
// void testGetAllConnectionStatus_ShouldReturnStatusMap() {
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
// machineInfoService.connect(machineInfo);
//
// var statusMap = machineInfoService.getAllConnectionStatus();
// assertFalse(statusMap.isEmpty());
// assertTrue(statusMap.containsValue(ConnectionStatus.CONNECTING));
// }
//}

View File

@ -0,0 +1,218 @@
//package com.casic.machine.service.impl;
//
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//import com.casic.commons.exception.ServiceException;
//import com.casic.commons.utils.AliOssUtil;
//import com.casic.machine.entity.MachineInfo;
//import com.casic.machine.entity.SecretKey;
//import com.casic.machine.dto.SecretKeyDto;
//import com.casic.machine.mapper.SecretServiceMapper;
//import com.casic.machine.service.MachineInfoService;
//import com.jayway.jsonpath.internal.Utils;
//import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.Test;
//import org.mockito.InjectMocks;
//import org.mockito.Mock;
//import org.mockito.MockitoAnnotations;
//import org.springframework.beans.BeanUtils;
//import org.springframework.core.io.InputStreamResource;
//import org.springframework.http.ResponseEntity;
//import org.springframework.mock.web.MockMultipartFile;
//import org.springframework.test.util.ReflectionTestUtils;
//
//import java.io.ByteArrayInputStream;
//import java.io.IOException;
//import java.util.Collections;
//import java.util.List;
//
//import static org.junit.jupiter.api.Assertions.*;
//import static org.mockito.ArgumentMatchers.any;
//import static org.mockito.Mockito.*;
//
//@SuppressWarnings("unchecked")
//public class SecretKeyServiceImplTest {
//
// @InjectMocks
// private SecretKeyServiceImpl secretKeyService;
//
// @Mock
// private AliOssUtil aliOssUtil;
//
// @Mock
// private SecretServiceMapper secretServiceMapper;
//
// @Mock
// private MachineInfoService machineInfoService;
//
// private SecretKeyDto validSecretKeyDto;
// private MockMultipartFile mockFile;
// private final Long TEST_SECRET_KEY_ID = 1L;
// private final String TEST_FILE_NAME = "test_key.pem";
// private final String TEST_PATH = "https://bucket.endpoint/test_key.pem";
//
// @BeforeEach
// public void setUp() throws IOException {
// MockitoAnnotations.openMocks(this);
// validSecretKeyDto = new SecretKeyDto();
// validSecretKeyDto.setName("Test Key");
// validSecretKeyDto.setDescription("Test secret key");
//
// // 创建模拟文件
// byte[] fileContent = "test content".getBytes();
// mockFile = new MockMultipartFile(
// "file",
// TEST_FILE_NAME,
// "application/octet-stream",
// new ByteArrayInputStream(fileContent)
// );
//
// // 模拟 OSS 工具返回值
// when(aliOssUtil.save(any(MockMultipartFile.class))).thenReturn(TEST_FILE_NAME);
//
// }
//
// // ======================= 新增密钥测试 =======================
// @Test
// void testAddSecretKey_ValidData_ShouldSucceed() throws IOException {
// // 执行新增
// boolean result = secretKeyService.addSecretKey(validSecretKeyDto, mockFile);
//
// // 验证 OSS 保存调用
// verify(aliOssUtil, times(1)).save(mockFile);
//
// // 验证实体属性
// SecretKey savedKey = new SecretKey();
// BeanUtils.copyProperties(validSecretKeyDto, savedKey);
// savedKey.setFileName(TEST_FILE_NAME);
// savedKey.setPath(TEST_PATH);
//
// // 验证 Mapper 调用
// verify(secretServiceMapper, times(1)).insert(savedKey);
// assertTrue(result);
// }
//
// @Test
// void testAddSecretKey_NullFile_ShouldThrowException() {
// assertThrows(ServiceException.class, () -> {
// secretKeyService.addSecretKey(validSecretKeyDto, null);
// }, "文件为空");
// }
//
// // ======================= 绑定机器测试 =======================
// @Test
// void testBindingMachine_ValidIds_ShouldUpdateMachine() {
// // 模拟机器列表
// List<Long> machineIds = Collections.singletonList(1L);
// when(machineInfoService.listByIds(machineIds)).thenReturn(Collections.singletonList(new MachineInfo()));
//
// secretKeyService.bindingMachine(TEST_SECRET_KEY_ID, machineIds);
//
// // 验证机器信息更新
// verify(machineInfoService, times(1)).listByIds(machineIds);
// machineIds.forEach(id -> {
// verify(machineInfoService, times(1)).update(any());
// });
// }
//
// // ======================= 更新密钥测试 =======================
// @Test
// void testUpdateSecretKey_WithNewFile_ShouldUpdatePath() throws IOException {
// MockMultipartFile newFile = new MockMultipartFile(
// "file",
// "new_key.pem",
// "application/octet-stream",
// new ByteArrayInputStream("new content".getBytes())
// );
// when(aliOssUtil.save(newFile)).thenReturn("new_key.pem");
//
// validSecretKeyDto.setId(TEST_SECRET_KEY_ID);
// boolean result = secretKeyService.updateSecretKey(validSecretKeyDto, newFile);
//
// // 验证 OSS 调用和路径更新
// verify(aliOssUtil, times(1)).save(newFile);
// SecretKey updatedKey = new SecretKey();
// BeanUtils.copyProperties(validSecretKeyDto, updatedKey);
// updatedKey.setFileName("new_key.pem");
// updatedKey.setPath("https://bucket.endpoint/new_key.pem");
// verify(secretServiceMapper, times(1)).updateById(updatedKey);
// assertTrue(result);
// }
//
// // ======================= 删除密钥测试 =======================
// @Test
// void testDeleteSecretKey_ValidId_ShouldDeleteFileAndRecord() {
// SecretKey secretKey = new SecretKey();
// secretKey.setId(TEST_SECRET_KEY_ID);
// secretKey.setFileName(TEST_FILE_NAME);
// when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
//
// boolean result = secretKeyService.deleteSecretKey(TEST_SECRET_KEY_ID);
//
// // 验证 OSS 删除和 Mapper 调用
// verify(aliOssUtil, times(1)).deleteFile(TEST_FILE_NAME);
// verify(secretServiceMapper, times(1)).deleteById(TEST_SECRET_KEY_ID);
// assertTrue(result);
// }
//
// // ======================= 列表查询测试 =======================
// @Test
// void testListSecretKey_WithNameFilter_ShouldReturnMatchedRecords() {
// SecretKeyDto queryDto = new SecretKeyDto();
// queryDto.setName("Test");
// QueryWrapper<SecretKey> wrapper = new QueryWrapper<>();
// wrapper.like("name", "Test");
//
// when(secretServiceMapper.selectPage(any(), any())).thenReturn(new Page<>());
//
// secretKeyService.listSecretKey(queryDto);
// verify(secretServiceMapper, times(1)).selectPage(any(), wrapper);
// }
//
// // ======================= 文件下载测试 =======================
// @Test
// void testDownloadSecretKeyFile_ValidId_ShouldReturnResponseEntity() throws IOException {
// SecretKey secretKey = new SecretKey();
// secretKey.setFileName(TEST_FILE_NAME);
// when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
// InputStreamResource inputStreamResource = new InputStreamResource(
// new ByteArrayInputStream("test content".getBytes())
// );
// when(aliOssUtil.downloadFile(TEST_FILE_NAME)).thenReturn(
// ResponseEntity.ok(inputStreamResource)
// );
//
// ResponseEntity<InputStreamResource> response = secretKeyService.downloadSecretKeyFile(TEST_SECRET_KEY_ID);
// assertNotNull(response);
// assertEquals("test content", Utils.toString(response.getBody().getInputStream()));
// }
//
// // ======================= 批量删除测试 =======================
// @Test
// void testDeleteList_ValidIds_ShouldSubmitAsyncDelete() {
// List<Long> ids = Collections.singletonList(TEST_SECRET_KEY_ID);
// SecretKey secretKey = new SecretKey();
// secretKey.setFileName(TEST_FILE_NAME);
// when(secretServiceMapper.selectBatchIds(ids)).thenReturn(Collections.singletonList(secretKey));
//
// secretKeyService.deleteList(ids);
//
// // 验证异步任务提交
// verify(secretKeyService.FILE_DELETE_EXECUTOR, times(1)).execute(any(Runnable.class));
// verify(secretServiceMapper, times(1)).deleteBatchIds(ids);
// }
//
// // ======================= 异常处理测试 =======================
// @Test
// void testDeleteSecretKey_FileDeleteFailed_ShouldThrowException() {
// aliOssUtil.deleteFile(TEST_FILE_NAME);
// SecretKey secretKey = new SecretKey();
// secretKey.setId(TEST_SECRET_KEY_ID);
// secretKey.setFileName(TEST_FILE_NAME);
// when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
//
// assertThrows(ServiceException.class, () -> {
// secretKeyService.deleteSecretKey(TEST_SECRET_KEY_ID);
// }, "删除文件失败");
// }
//}

View File

@ -14,6 +14,7 @@
<module>module-infra-biz</module>
<module>module-system-api</module>
<module>module-system-biz</module>
<module>module-ci-machine</module>
</modules>
<artifactId>modules</artifactId>

View File

@ -25,6 +25,11 @@
<artifactId>module-system-biz</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cd.casic.boot</groupId>
<artifactId>module-ci-machine</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cd.casic.boot</groupId>

View File

@ -156,3 +156,12 @@ logging:
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # 禁用Spring Boot 3.X 存在部分错误的 WARN 提示
debug: false
#阿里云oss
aliyun:
oss:
endpoint: https://oss-cn-beijing.aliyuncs.com
accessKeyId: LTAI5tPCKES4ZxdRKybGjJf4
accessKeySecret: mtX94qmxnWR2FDem0z38qjv0rrILSE
bucketName: kkt-web-tlias