From be07dc0e0a2f1a8b796356db4d960fadff0b3928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E6=BD=87=E5=87=AF?= Date: Mon, 9 Jun 2025 18:05:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=BA=E5=99=A8=E7=AE=A1=E7=90=86=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contants/MachineErrorCodeConstants.java | 21 + .../controller/MachineInfoController.java | 269 ++-- .../controller/SecretKeyController.java | 134 +- .../machine/controller/vo/MachineInfoDto.java | 46 - .../machine/controller/vo/MachineInfoVO.java | 64 + .../machine/controller/vo/SecretKeyDto.java | 35 - .../machine/controller/vo/SecretKeyVO.java | 43 + .../{MachineInfo.java => MachineInfoDO.java} | 43 +- .../{SecretKey.java => SecretKeyDO.java} | 11 +- .../machine/dal/mysql/MachineInfoMapper.java | 45 +- .../machine/dal/mysql/SecretKeyMapper.java | 29 + .../dal/mysql/SecretServiceMapper.java | 9 - .../machine/enums/AuthenticationType.java | 11 +- .../machine/enums/MachineInfoStatus.java | 8 +- .../module/machine/enums/MachineInfoType.java | 12 +- .../machine/enums/MachineProxyStatus.java | 1 + .../enums/PermissionExceptionEnum.java | 19 - .../machine/handler/ConnectionSession.java | 1277 +++++++++-------- .../machine/service/MachineInfoService.java | 25 +- .../machine/service/SecretKeyService.java | 21 +- .../service/impl/MachineEnvServiceImpl.java | 8 +- .../service/impl/MachineProxyServiceImpl.java | 7 +- .../service/impl/MachineinfoServiceImpl.java | 724 +++++----- .../service/impl/SecretKeyServiceImpl.java | 316 ++-- 24 files changed, 1673 insertions(+), 1505 deletions(-) delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoDto.java create mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoVO.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyDto.java create mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyVO.java rename modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/{MachineInfo.java => MachineInfoDO.java} (58%) rename modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/{SecretKey.java => SecretKeyDO.java} (77%) create mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretKeyMapper.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretServiceMapper.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/PermissionExceptionEnum.java diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/contants/MachineErrorCodeConstants.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/contants/MachineErrorCodeConstants.java index a69ebcbc..e42240bd 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/contants/MachineErrorCodeConstants.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/contants/MachineErrorCodeConstants.java @@ -8,6 +8,16 @@ 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 MACHINE_INFO_HOST_IP_NULL = new ErrorCode(1_003_000_000, "机器主机IP为空"); + ErrorCode MACHINE_INFO_USER_NAME_NULL = new ErrorCode(1_003_000_000, "机器用户名为空"); + ErrorCode MACHINE_INFO_TYPE_NULL = new ErrorCode(1_003_000_022, "机器类型为空"); + ErrorCode MACHINE_INFO_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_011, "机器类型不存在"); + ErrorCode MACHINE_INFO_TAG_NULL = new ErrorCode(1_003_000_044, "机器唯一标识为空"); + ErrorCode MACHINE_INFO_TAG_EXISTS = new ErrorCode(1_003_000_044, "机器唯一标识已存在"); + ErrorCode MACHINE_INFO_AUTHENTICATION_TYPE_NULL = new ErrorCode(1_003_000_044, "机器认证类型为空"); + ErrorCode MACHINE_INFO_AUTHENTICATION_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_044, "机器认证类型不存在"); + ErrorCode MACHINE_ENABLE = new ErrorCode(1_003_000_044, "机器启用中"); + ErrorCode MACHINE_UN_ENABLE = new ErrorCode(1_003_000_044, "机器不可用"); 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, "文件名为空"); @@ -15,6 +25,8 @@ public interface MachineErrorCodeConstants { 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, "机器不存在"); @@ -26,6 +38,15 @@ public interface MachineErrorCodeConstants { ErrorCode MACHINE_PROXY_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_007, "机器代理类型不存在"); ErrorCode MACHINE_PROXY_IS_ONLINE = new ErrorCode(1_003_000_007, "机器代理在线,不能删除"); + // ========== 密钥模块 1-003-000-010 ========== + ErrorCode SECRET_KEY_NULL = new ErrorCode(1_003_000_010, "密钥为空"); + ErrorCode SECRET_KEY_NOT_EXISTS = new ErrorCode(1_003_000_010, "密钥不存在"); + ErrorCode SECRET_KEY_NAME_ILLEGAL = new ErrorCode(1_003_000_010, "密钥名称不合法"); + ErrorCode SECRET_KEY_PATH_ILLEGAL = new ErrorCode(1_003_000_010, "密钥路径不合法"); + ErrorCode SECRET_KEY_PATH_NULL = new ErrorCode(1_003_000_010, "密钥路径为空"); + ErrorCode SECRET_KEY_UPLOAD_FAIL = new ErrorCode(1_003_000_010, "密钥上传失败"); + + ErrorCode OSS_PARAM_NULL = new ErrorCode(1_003_000_011, "oss参数无法读取"); ErrorCode SECRETKEY_NULL = new ErrorCode(1_003_000_012, "密钥为空"); } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/MachineInfoController.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/MachineInfoController.java index fb38425e..34c94923 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/MachineInfoController.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/MachineInfoController.java @@ -1,126 +1,143 @@ -//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 add(@RequestBody MachineInfoDto machineInfoDto) { -// return success(machineInfoService.addMachineInfo(machineInfoDto)); -// } -// -// -// @PostMapping("/list") -// @Operation(summary = "获取机器信息列表") -// public CommonResult> list(@RequestBody MachineInfoDto machineInfoDto) { -// return success(machineInfoService.listMachineInfo(machineInfoDto)); -// } -// -// @PutMapping("/update") -// @Operation(summary = "编辑机器信息") -// public CommonResult update(@RequestBody MachineInfoDto machineInfoDto) { -// return success(machineInfoService.updateMachineInfo(machineInfoDto)); -// } -// -// @PutMapping("/updateStatus") -// @Operation(summary = "机器启用/停用") -// public CommonResult updateStatus(@RequestBody MachineInfoDto machineInfoDto) { -// return success(machineInfoService.updateStatus(machineInfoDto)); -// } -// -// @PutMapping("/bindingSecretKey") -// @Operation(summary = "绑定密钥") -// public CommonResult bindingSecretKey(@RequestBody MachineInfoDto machineInfoDto) { -// return success(machineInfoService.bindingSecretKey(machineInfoDto)); -// } -// -// @DeleteMapping("/delete") -// @Operation(summary = "机器信息删除") -// public CommonResult delete(@RequestParam Long machineInfoId) { -// machineInfoService.deleteMachineInfo(machineInfoId); -// return success(true); -// } -// -// @DeleteMapping("/deleteList") -// @Operation(summary = "批量删除机器信息") -// public CommonResult deleteList(@RequestParam String machineInfoIds) { -// machineInfoService.deleteList(machineInfoIds); -// return success(true); -// } -// -// @PostMapping("/test") -// @Operation(summary = "测试机器连接") -// public CommonResult testConnection(@RequestParam Long id) { -// return success(machineInfoService.testConnection(id)); -// } -// -// @GetMapping("/status/{machineName}") -// @Operation(summary = "获取机器连接状态") -// public CommonResult getConnectionStatus(@PathVariable String machineName) { -// return success(machineInfoService.getConnectionStatus(machineName)); -// } -// -// @GetMapping("/status/all") -// @Operation(summary = "获取所有机器连接状态") -// public CommonResult> getAllConnectionStatus() { -// return success(machineInfoService.getAllConnectionStatus()); -// } -// -// @PostMapping("/connect") -// @Operation(summary = "建立机器连接") -// public CommonResult connect(@RequestBody MachineInfo machineInfo) { -// return success(machineInfoService.connect(machineInfo)); -// } -// -// @PostMapping("/disconnect/{sessionId}") -// @Operation(summary = "断开机器连接") -// public CommonResult disconnect(@PathVariable String sessionId) { -// return success(machineInfoService.disconnect(sessionId)); -// } -// -// @PostMapping("/execute/{sessionId}") -// @Operation(summary = "执行远程命令") -// public CommonResult executeCommand( -// @PathVariable String sessionId, -// @RequestBody String command) { -// -// return success(machineInfoService.executeCommand(sessionId, command)); -// } -// -// @PostMapping("/upload/{sessionId}") -// @Operation(summary = "上传文件到远程机器") -// public CommonResult uploadFile( -// @PathVariable String sessionId, -// @RequestParam String localFilePath, -// @RequestParam String remoteFilePath) { -// return success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath)); -// } -// -// @PostMapping("/download/{sessionId}") -// @Operation(summary = "从远程机器下载文件") -// public CommonResult downloadFile( -// @PathVariable String sessionId, -// @RequestParam String remoteFilePath, -// @RequestParam String localFilePath) { -// return success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath)); -// } -//} +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.MachineInfoDO; +import cd.casic.module.machine.enums.ConnectionStatus; +import cd.casic.module.machine.service.MachineInfoService; +import cd.casic.module.machine.controller.vo.MachineInfoVO; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Map; +import static cd.casic.framework.commons.pojo.CommonResult.success; + +@RestController +@Tag(name = "机器信息管理") +@RequestMapping("/ci/machineInfo") +@Validated +public class MachineInfoController { + @Resource + private MachineInfoService machineInfoService; + + @PostMapping("/create") + @Operation(summary = "新增机器信息") +// @PreAuthorize("@ss.hasPermission('ci:machineInfo:create')") + public CommonResult createMachine(@Valid @RequestBody MachineInfoVO machineInfoVO) { + Long id = machineInfoService.createMachine(machineInfoVO); + return success(id); + } + + + @PutMapping("/update") + @Operation(summary = "编辑机器信息") +// @PreAuthorize("@ss.hasPermission('ci:machineInfo:update')") + public CommonResult updateMachineInfo(@Valid @RequestBody MachineInfoVO machineInfoVO) { + machineInfoService.updateMachineInfo(machineInfoVO); + return success(true); + } + + @PutMapping("/updateStatus") + @Operation(summary = "机器启用/停用") +// @PreAuthorize("@ss.hasPermission('ci:machineInfo:status')") + public CommonResult updateStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) { + Integer newStatus = machineInfoService.updateStatus(id, status); + return success(newStatus); + } + + @PostMapping("/list") + @Operation(summary = "获取机器信息列表") + public CommonResult> list(@Valid @RequestBody MachineInfoVO machineInfoVO) { + PageResult pageResult = machineInfoService.listMachineInfo(machineInfoVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + return success(BeanUtils.toBean(pageResult, MachineInfoVO.class)); + } + + @PutMapping("/bindingSecretKey") + @Operation(summary = "绑定密钥") +// @PreAuthorize("@ss.hasPermission('ci:machineInfo:binding')") + public CommonResult bindingSecretKey(@RequestParam("ids") List ids, @RequestParam("secretKeyId") Long secretKeyId) { + machineInfoService.bindingSecretKey(ids,secretKeyId); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "机器信息删除") +// @PreAuthorize("@ss.hasPermission('ci:machineInfo:delete')") + public CommonResult deleteMachineInfo(@RequestParam("id") Long id) { + machineInfoService.deleteMachineInfo(id); + return success(true); + } + + @DeleteMapping("/deleteList") + @Operation(summary = "批量删除机器信息") +// @PreAuthorize("@ss.hasPermission('ci:machineInfo:delete')") + public CommonResult deleteMachineInfoList(@RequestParam("machineInfoIds") String ids) { + machineInfoService.deleteMachineInfoList(ids); + return success(true); + } + + @PostMapping("/test") + @Operation(summary = "测试机器连接") + public CommonResult testConnection(@RequestParam Long id) { + return success(machineInfoService.testConnection(id)); + } + + @GetMapping("/status/{machineName}") + @Operation(summary = "获取机器连接状态") + public CommonResult getConnectionStatus(@PathVariable String machineName) { + return success(machineInfoService.getConnectionStatus(machineName)); + } + + @GetMapping("/status/all") + @Operation(summary = "获取所有机器连接状态") + public CommonResult> getAllConnectionStatus() { + return success(machineInfoService.getAllConnectionStatus()); + } + + @PostMapping("/connect") + @Operation(summary = "建立机器连接") + public CommonResult connect(@Valid @RequestBody MachineInfoDO machineInfoDO) { + return success(machineInfoService.connect(machineInfoDO)); + } + + @PostMapping("/disconnect/{sessionId}") + @Operation(summary = "断开机器连接") + public CommonResult disconnect(@PathVariable String sessionId) { + return success(machineInfoService.disconnect(sessionId)); + } + + @PostMapping("/execute/{sessionId}") + @Operation(summary = "执行远程命令") + public CommonResult executeCommand( + @PathVariable String sessionId, + @RequestBody String command) { + + return success(machineInfoService.executeCommand(sessionId, command)); + } + + @PostMapping("/upload/{sessionId}") + @Operation(summary = "上传文件到远程机器") + public CommonResult uploadFile( + @PathVariable String sessionId, + @RequestParam String localFilePath, + @RequestParam String remoteFilePath) { + return success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath)); + } + + @PostMapping("/download/{sessionId}") + @Operation(summary = "从远程机器下载文件") + public CommonResult downloadFile( + @PathVariable String sessionId, + @RequestParam String remoteFilePath, + @RequestParam String localFilePath) { + return success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath)); + } +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/SecretKeyController.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/SecretKeyController.java index 4c204ae1..f75f3168 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/SecretKeyController.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/SecretKeyController.java @@ -1,56 +1,78 @@ -//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 add(@RequestBody SecretKeyDto secretKeyDto) throws Exception { -// return success(secretKeyService.addSecretKey(secretKeyDto)); -// } -// -// @PutMapping("/bindingMachine") -// @Operation(summary = "绑定机器") -// public CommonResult bindingMachine(@RequestParam("secretKeyId") Long secretKeyId, @RequestParam("machineIds") List machineIds) { -// secretKeyService.bindingMachine(secretKeyId, machineIds); -// return success(true); -// } -// -// @PutMapping("/update") -// @Operation(summary = "编辑密钥信息") -// public CommonResult update(@RequestBody SecretKeyDto secretKeyDto) { -// return success(secretKeyService.updateSecretKey(secretKeyDto)); -// } -// -// @DeleteMapping("/deleteList") -// @Operation(summary = "批量删除密钥") -// public CommonResult deleteList(@RequestParam("secretKeyId") List secretKeyIds) { -// return success(secretKeyService.deleteList(secretKeyIds)); -// } -// -// @PostMapping("/list") -// @Operation(summary = "获取密钥信息列表") -// public CommonResult> list(@RequestBody SecretKeyDto secretKeyDto) { -// return success(secretKeyService.listSecretKey(secretKeyDto)); -// } -// -// -//} +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.SecretKeyDO; +import cd.casic.module.machine.service.SecretKeyService; +import cd.casic.module.machine.controller.vo.SecretKeyVO; +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.List; +import static cd.casic.framework.commons.pojo.CommonResult.success; + +@RestController +@RequestMapping("/ci/secretKey") +@Tag(name = "密钥管理") +@Validated +public class SecretKeyController { + @Resource + private SecretKeyService secretKeyService; + + @PostMapping(value = "/create") + @Operation(summary = "新增密钥") +// @PreAuthorize("@ss.hasPermission('ci:secretKey:create')") + public CommonResult createSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) throws Exception { + Long secretKeyId = secretKeyService.createSecretKey(secretKeyVO); + return success(secretKeyId); + } + + @PutMapping("/update") + @Operation(summary = "编辑密钥信息") +// @PreAuthorize("@ss.hasPermission('ci:secretKey:update')") + public CommonResult updateSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) { + secretKeyService.updateSecretKey(secretKeyVO); + return success(true); + } + + @PutMapping("/bindingMachine") + @Operation(summary = "绑定机器") //todo解绑机器 +// @PreAuthorize("@ss.hasPermission('ci:secretKey:binding')") + public CommonResult bindingMachine(@RequestParam("id") Long id, @RequestParam("machineInfoIds") List machineInfoId) { + secretKeyService.bindingMachine(id, machineInfoId); + return success(true); + } + + @GetMapping("/getSecretKey") + @Operation(summary = "获取机器的环境变量") + public CommonResult getSecretKey(@RequestParam("id") Long id) { + SecretKeyVO secretKeyVO = secretKeyService.getSecretKey(id); + return success(secretKeyVO); + } + + + @DeleteMapping("/deleteList") + @Operation(summary = "批量删除密钥") +// @PreAuthorize("@ss.hasPermission('ci:secretKey:delete')") + public CommonResult deleteSecretKeyList(@RequestParam("ids") List ids) { + secretKeyService.deleteSecretKeyList(ids); + return success(true); + } + + @PostMapping("/list") + @Operation(summary = "获取密钥信息列表") + public CommonResult> getSecretKeypage(@Valid @RequestBody SecretKeyVO secretKeyVO) { + PageResult pageResult = secretKeyService.getSecretKeypage(secretKeyVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + return success(BeanUtils.toBean(pageResult,SecretKeyVO.class)); + } + + +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoDto.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoDto.java deleted file mode 100644 index 81371e4a..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoDto.java +++ /dev/null @@ -1,46 +0,0 @@ -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; -} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoVO.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoVO.java new file mode 100644 index 00000000..be6964bc --- /dev/null +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/MachineInfoVO.java @@ -0,0 +1,64 @@ +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.util.Date; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "管理后台 - 机器信息 Response VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class MachineInfoVO extends PageParam { + + @Schema(description = "机器ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00") + private Date createTime; + + @Schema(description = "更新时间", example = "2023-06-15T10:30:00") + private Date updateTime; + + @Schema(description = "机器名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "server-01") + private String name; + + @Schema(description = "机器标签,唯一标识", example = "production,web-server") + private String tag; + + @Schema(description = "主机IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100") + private String hostIp; + + @Schema(description = "机器描述", example = "生产环境Web服务器") + private String description; + + @Schema(description = "登录用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin") + private String username; + + @Schema(description = "机器状态", example = "online,offline,maintenance") + private Integer status; + + @Schema(description = "SSH端口", example = "22") + private Integer sshPort; + + @Schema(description = "登录密码", example = "******") + private String password; + + @Schema(description = "密钥ID", example = "5") + private Long secretKeyId; + + @Schema(description = "代理ID", example = "101") + private Long machineProxyId; + + @Schema(description = "认证类型", example = "password,key") + private Integer authenticationType; + + @Schema(description = "机器信息类型", example = "Linux,Windows") + private Integer machineInfoType; + + +} \ No newline at end of file diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyDto.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyDto.java deleted file mode 100644 index c23b3836..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyDto.java +++ /dev/null @@ -1,35 +0,0 @@ -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 machineInfoIds; -} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyVO.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyVO.java new file mode 100644 index 00000000..9245edea --- /dev/null +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/controller/vo/SecretKeyVO.java @@ -0,0 +1,43 @@ +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; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "管理后台 - 密钥信息 Response VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) // 添加链式调用支持 +public class SecretKeyVO extends PageParam { + + @Schema(description = "密钥ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "密钥名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "生产环境密钥") + private String name; + + @Schema(description = "密钥描述", example = "用于加密敏感数据的密钥") + private String description; + + @Schema(description = "存储路径(本地上传文件路径)", example = "/data/secret_keys/") + private String path; + + @Schema(description = "文件名", example = "key.pem") + private String fileName; + + @Schema(description = "密钥密码", example = "******") + private String password; + + @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 = "关联的机器ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 2048]") + private List machineInfoIds; +} \ No newline at end of file diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/MachineInfo.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/MachineInfoDO.java similarity index 58% rename from modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/MachineInfo.java rename to modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/MachineInfoDO.java index ab3d1d50..0b25a446 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/MachineInfo.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/MachineInfoDO.java @@ -1,24 +1,35 @@ package cd.casic.module.machine.dal.dataobject; +import cd.casic.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; 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 { +public class MachineInfoDO extends BaseDO { + + /** + * 机器id + */ + @TableId + private Long id; + + @TableField(value = "name") private String name; + /** + * 机器唯一标识 + */ @TableField(value = "tag") private String tag; @@ -28,18 +39,11 @@ public class MachineInfo extends BaseEntity { @TableField(value = "description") private String description; - @TableField(exist = false) - private MachineInfoType machineInfoType; + @TableField(value = "machine_info_type") + private Integer 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 = "status") + private Integer status; //用户名 @TableField(value = "username") @@ -58,12 +62,7 @@ public class MachineInfo extends BaseEntity { @TableField(value = "machine_proxy_id") private Long machineProxyId; - @TableField(value = "authentication_type_code") - private int authenticationTypeCode; + @TableField(value = "authentication_type") + private Integer authenticationType; - @TableField(exist = false) - private AuthenticationType authenticationType; - - @TableField(value = "is_proxy") - private int isProxy; } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/SecretKey.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/SecretKeyDO.java similarity index 77% rename from modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/SecretKey.java rename to modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/SecretKeyDO.java index a1d4e820..cab9d3b4 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/SecretKey.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/dataobject/SecretKeyDO.java @@ -1,6 +1,8 @@ package cd.casic.module.machine.dal.dataobject; +import cd.casic.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; @@ -12,7 +14,14 @@ import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor @TableName(value = "machine_secret_key") -public class SecretKey extends BaseEntity{ +public class SecretKeyDO extends BaseDO { + + /** + * 密钥id + */ + @TableId + private Long id; + @TableField(value = "name") private String name; diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/MachineInfoMapper.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/MachineInfoMapper.java index d8a4257b..d0a967fa 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/MachineInfoMapper.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/MachineInfoMapper.java @@ -1,11 +1,50 @@ 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.MachineEnvVO; +import cd.casic.module.machine.controller.vo.MachineInfoVO; import cd.casic.module.machine.dal.dataobject.MachineEnvDO; -import cd.casic.module.machine.dal.dataobject.MachineInfo; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cd.casic.module.machine.dal.dataobject.MachineInfoDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper -public interface MachineInfoMapper extends BaseMapperX { +public interface MachineInfoMapper extends BaseMapperX { + default Boolean existsByTag(String tag){ + return selectOne(new QueryWrapper().eq("tag", tag))!=null; + } + default void updateStatus(Long machineInfoId, Integer status){ + UpdateWrapper set = new UpdateWrapper<>(); + set.eq("id", machineInfoId).set("status", status); + this.update(null,set); + } + default void bindingSecretKey(List machineInfoIds, Long secretKeyId){ + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(MachineInfoDO::getSecretKeyId, secretKeyId) + .in(MachineInfoDO::getId, machineInfoIds); + this.update(null, wrapper); + } + default void unBindingSecretKey(List secretKeyId){ + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(MachineInfoDO::getSecretKeyId, null) + .in(MachineInfoDO::getSecretKeyId, secretKeyId); + this.update(null, wrapper); + } + default PageResult selectPage(MachineInfoVO machineInfoVO){ + return selectPage(machineInfoVO,new LambdaQueryWrapperX() + .likeIfPresent(MachineInfoDO::getName, machineInfoVO.getName()) + .likeIfPresent(MachineInfoDO::getTag, machineInfoVO.getTag()) + .likeIfPresent(MachineInfoDO::getDescription, machineInfoVO.getDescription()) + .likeIfPresent(MachineInfoDO::getUsername, machineInfoVO.getUsername()) + .eqIfPresent(MachineInfoDO::getHostIp, machineInfoVO.getHostIp())); + } + + } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretKeyMapper.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretKeyMapper.java new file mode 100644 index 00000000..05166abe --- /dev/null +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretKeyMapper.java @@ -0,0 +1,29 @@ +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.SecretKeyVO; +import cd.casic.module.machine.dal.dataobject.MachineInfoDO; +import cd.casic.module.machine.dal.dataobject.SecretKeyDO; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface SecretKeyMapper extends BaseMapperX { + //查询列表 + default PageResult selectPage(SecretKeyVO secretKeyVO){ + return selectPage(secretKeyVO,new LambdaQueryWrapperX() + .likeIfPresent(SecretKeyDO::getName,secretKeyVO.getName()) + .likeIfPresent(SecretKeyDO::getDescription,secretKeyVO.getDescription())); + + } + + default void bindingMachine(Long machineInfoId, List secretKeyId){ + UpdateWrapper set = new UpdateWrapper<>(); + set.eq("id", secretKeyId).set("machineInfoId", machineInfoId); + this.update(null,set); + } +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretServiceMapper.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretServiceMapper.java deleted file mode 100644 index 26e92c18..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/mysql/SecretServiceMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -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 { -} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/AuthenticationType.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/AuthenticationType.java index 80eb092a..71f8314c 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/AuthenticationType.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/AuthenticationType.java @@ -1,16 +1,25 @@ 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 AuthenticationType implements CodeEnum { +public enum AuthenticationType implements IntArrayValuable { PASSWORD(1,"密码认证"), SECRET_KEY(2,"密钥认证"); + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AuthenticationType::getCode).toArray(); private final int code; private final String message; + + @Override + public int[] array() { + return ARRAYS; + } } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoStatus.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoStatus.java index df37b1dd..74f3d869 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoStatus.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoStatus.java @@ -1,16 +1,22 @@ package cd.casic.module.machine.enums; +import cd.casic.framework.commons.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor -public enum MachineInfoStatus implements CodeEnum { +public enum MachineInfoStatus implements IntArrayValuable { ENABLE(1,"启用"), UN_ENABLE(0,"停用"); private final int code; private final String message; + + @Override + public int[] array() { + return new int[0]; + } } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoType.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoType.java index 20084334..2a168b31 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoType.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineInfoType.java @@ -1,16 +1,24 @@ 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 MachineInfoType implements CodeEnum { +public enum MachineInfoType implements IntArrayValuable { Linux(1,"Linux"), WINDOWS(2,"Windows"); - + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineInfoType::getCode).toArray(); private final int code; private final String message; + + @Override + public int[] array() { + return ARRAYS; + } } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineProxyStatus.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineProxyStatus.java index 12864b47..e162c82a 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineProxyStatus.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/MachineProxyStatus.java @@ -6,6 +6,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; +import java.util.List; @Getter @AllArgsConstructor diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/PermissionExceptionEnum.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/PermissionExceptionEnum.java deleted file mode 100644 index c98b9332..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/PermissionExceptionEnum.java +++ /dev/null @@ -1,19 +0,0 @@ -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; -} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/ConnectionSession.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/ConnectionSession.java index dcbfe7e7..88949967 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/ConnectionSession.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/ConnectionSession.java @@ -1,639 +1,640 @@ -//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); +package cd.casic.module.machine.handler; +import cd.casic.module.machine.controller.vo.SecretKeyVO; +import cd.casic.module.machine.dal.dataobject.MachineInfoDO; +import cd.casic.module.machine.enums.AuthenticationType; +import cd.casic.module.machine.utils.AliYunOssClient; +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 MachineInfoDO 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(MachineInfoDO 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.getAuthenticationType() == AuthenticationType.SECRET_KEY.getCode()) { + // 密钥认证 + 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.getAuthenticationType() == AuthenticationType.PASSWORD.getCode()) { + // 密码认证 + 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.getSecretKey(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); // } -// } -// -// // 所有重试都失败 -// 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); -// } -// -// -// } -//} + } + } + + 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; + } + SecretKeyVO secretKey = secretKeyService.getSecretKey(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); + } + + + } +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineInfoService.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineInfoService.java index 23591523..1831eb68 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineInfoService.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineInfoService.java @@ -1,25 +1,26 @@ 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.controller.vo.MachineInfoVO; +import cd.casic.module.machine.dal.dataobject.MachineInfoDO; import cd.casic.module.machine.enums.ConnectionStatus; -import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.bind.annotation.RequestParam; +import java.util.List; import java.util.Map; public interface MachineInfoService { - boolean addMachineInfo(MachineInfoDto MachineInfoDto); + Long createMachine(MachineInfoVO MachineInfoVO); - PageResult listMachineInfo(MachineInfoDto MachineInfoDto); + PageResult listMachineInfo(MachineInfoVO MachineInfoVO); - boolean updateMachineInfo(MachineInfoDto machineInfoDto); + void updateMachineInfo(MachineInfoVO machineInfoVO); - boolean updateStatus(MachineInfoDto machineInfoDto); + Integer updateStatus(Long machineInfoId, Integer status); - boolean bindingSecretKey(MachineInfoDto machineInfoDto); + void bindingSecretKey(List machineInfoIds, Long secretKeyId); - void deleteList(String machineInfoIds); + void deleteMachineInfoList(String machineInfoIds); void deleteMachineInfo(Long machineInfoId); @@ -50,10 +51,10 @@ public interface MachineInfoService { /** * 建立机器连接 * - * @param machineInfo 机器信息 + * @param machineInfoDO 机器信息 * @return 连接会话ID */ - String connect(MachineInfo machineInfo); + String connect(MachineInfoDO machineInfoDO); /** * 断开机器连接 @@ -91,4 +92,6 @@ public interface MachineInfoService { * @return 操作结果 */ boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath); + + void unBindingSecretKey(List SecretKeyIds); } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/SecretKeyService.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/SecretKeyService.java index f9887229..66397635 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/SecretKeyService.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/SecretKeyService.java @@ -1,24 +1,25 @@ 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 cd.casic.module.machine.dal.dataobject.SecretKeyDO; +import cd.casic.module.machine.controller.vo.SecretKeyVO; +import jakarta.validation.Valid; import java.util.List; -public interface SecretKeyService extends IService { - boolean addSecretKey(SecretKeyDto secretKeyDto) throws Exception; +public interface SecretKeyService{ + Long createSecretKey(@Valid SecretKeyVO secretKeyVO) throws Exception; - void bindingMachine(Long secretKeyId, List machineInfoIds); + void bindingMachine(Long id, List machineInfoId); - boolean updateSecretKey(SecretKeyDto secretKeyDto); + void updateSecretKey(@Valid SecretKeyVO secretKeyVO); - PageResult listSecretKey(SecretKeyDto secretKeyDto); + PageResult getSecretKeypage(@Valid SecretKeyVO secretKeyVO); - boolean deleteList(List secretKeyIds); + void deleteSecretKeyList(List ids); + + SecretKeyVO getSecretKey(Long id); } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineEnvServiceImpl.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineEnvServiceImpl.java index a4b4efe5..d9908cd8 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineEnvServiceImpl.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineEnvServiceImpl.java @@ -1,5 +1,4 @@ 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; @@ -45,11 +44,8 @@ public class MachineEnvServiceImpl implements MachineEnvService { @Override public MachineEnvVO getEnv(Long machineId) { - if (machineId == null) { - return null; - } - MachineEnvDO machineEnvDO = machineEnvMapper.selectById(machineId); - return machineEnvDO != null ? MachineEnvConvert.INSTANCE.convertToVO(machineEnvDO) : null; + MachineEnvDO machineEnvDO = validateMachineEnvExists(machineId); + return BeanUtils.toBean(machineEnvDO, MachineEnvVO.class); } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineProxyServiceImpl.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineProxyServiceImpl.java index e4c5d0be..d4523f9c 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineProxyServiceImpl.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineProxyServiceImpl.java @@ -6,10 +6,7 @@ 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; @@ -19,16 +16,14 @@ 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 +@Service("machineProxyService") public class MachineProxyServiceImpl implements MachineProxyService { @Resource private MachineProxyMapper machineProxyMapper; diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineinfoServiceImpl.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineinfoServiceImpl.java index 4dfa1245..87685435 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineinfoServiceImpl.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineinfoServiceImpl.java @@ -1,354 +1,370 @@ -//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 sessions = new ConcurrentHashMap<>(); -// -// /** -// * 机器名称 -> 会话ID -// */ -// private final Map 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 listMachineInfo(MachineInfoDto machineInfoDto) { -// QueryWrapper queryWrapper = getMachineInfoQueryWrapper(machineInfoDto); -// Page page = machineInfoMapper.selectPage( -// new Page<>(machineInfoDto.getPageIndex(), machineInfoDto.getPageSize()), -// queryWrapper -// ); -// -// List 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 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 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 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 getMachineInfoQueryWrapper(MachineInfoDto machineInfoDto) { -// QueryWrapper 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 getAllConnectionStatus() { -// Map 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(); -// } -//} +package cd.casic.module.machine.service.impl; +import cd.casic.framework.commons.pojo.PageResult; +import cd.casic.module.machine.controller.vo.SecretKeyVO; +import cd.casic.module.machine.enums.AuthenticationType; +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.MachineInfoVO; +import cd.casic.module.machine.dal.dataobject.MachineInfoDO; +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.service.SecretKeyService; +import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import cd.casic.framework.commons.util.object.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; +import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; + +/** + * 机器信息服务实现类 + */ +@Slf4j +@Service("machineInfoService") +public class MachineinfoServiceImpl implements MachineInfoService { + + @Resource + private SecretKeyService secretKeyService; + @Resource + private MachineInfoMapper machineInfoMapper; + @Resource + private ConnectionSession connectionSession; + /** + * 会话ID生成器 + */ + private final AtomicInteger sessionIdGenerator = new AtomicInteger(1000); + + /** + * 会话管理:会话ID -> 连接会话 + */ + private final Map sessions = new ConcurrentHashMap<>(); + + /** + * 机器名称 -> 会话ID + */ + private final Map machineSessionMapping = new ConcurrentHashMap<>(); + + @Override + public Long createMachine(MachineInfoVO machineInfoVO) { + validateMachineEnvAdd(machineInfoVO); + validateMachineTagUnique(machineInfoVO.getTag()); + MachineInfoDO machineInfoDO = BeanUtils.toBean(machineInfoVO, MachineInfoDO.class); + Long secretKeyId = machineInfoDO.getSecretKeyId(); + SecretKeyVO secretKey = secretKeyService.getSecretKey(secretKeyId); + if (secretKey==null){ + throw exception(SECRET_KEY_NOT_EXISTS); + } + machineInfoMapper.insert(machineInfoDO); + Long id = machineInfoDO.getId(); + return id; + } + + @Override + public void updateMachineInfo(MachineInfoVO machineInfoVO) { + validateMachineEnvAdd(machineInfoVO); + String newTag = machineInfoVO.getTag(); + MachineInfoDO machineInfoDO = validateMachineInfoExists(machineInfoVO.getId()); + String oldTag = machineInfoDO.getTag(); + if (!newTag.equals(oldTag)){ + validateMachineTagUnique(newTag); + } + BeanUtils.copyProperties(machineInfoVO, machineInfoDO); + machineInfoMapper.updateById(machineInfoDO); + } + + @Override + public Integer updateStatus(Long machineInfoId, Integer status) { + machineInfoMapper.updateStatus(machineInfoId, status); + return machineInfoMapper.selectById(machineInfoId).getStatus(); + } + + @Override + public PageResult listMachineInfo(MachineInfoVO machineInfoVO) { + return machineInfoMapper.selectPage(machineInfoVO); + } + + @Override + public void bindingSecretKey(List machineInfoId,Long secretKeyId) { + machineInfoMapper.bindingSecretKey(machineInfoId,secretKeyId); + } + + @Override + @Transactional//其中一个在线,那么就回滚 + public void deleteMachineInfoList(String machineInfoIds) { + List machineInfoIdList = Arrays.stream(machineInfoIds.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(Long::parseLong) + .toList(); + machineInfoIdList.forEach(this::deleteMachineInfo); + } + + @Override + public void deleteMachineInfo(Long machineInfoId) { + MachineInfoDO machineInfoDO = validateMachineInfoExists(machineInfoId); + validateMachineEnable(machineInfoDO); + machineInfoMapper.deleteById(machineInfoId); + } + + + @Override + public boolean testConnection(Long id) { + //先查询机器是否存在,在判断机器可用性 + MachineInfoDO machineInfoDO = validateMachineInfoExists(id); + validateMachineUnEnable(machineInfoDO); + log.info("测试机器连接: {}", machineInfoDO.getHostIp()); + connectionSession.setMachineInfo(machineInfoDO); + 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 getAllConnectionStatus() { + Map 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(MachineInfoDO machineInfoDO) { + //先查询机器是否存在,在判断机器可用性 + validateMachineUnEnable(machineInfoDO); + log.info("建立机器连接: {}", machineInfoDO.getHostIp()); + + // 检查是否已连接 + String existingSessionId = machineSessionMapping.get(machineInfoDO.getName()); + if (existingSessionId != null) { + ConnectionSession existingSession = sessions.get(existingSessionId); + if (existingSession != null && existingSession.getStatus() == ConnectionStatus.CONNECTED) { + log.info("机器已连接,返回现有会话: {}", machineInfoDO.getHostIp()); + return existingSessionId; + } + } + + try { + connectionSession.setMachineInfo(machineInfoDO); + + connectionSession.connect(); + + // 生成会话ID + String sessionId = generateSessionId(); + + // 保存会话 + sessions.put(sessionId, connectionSession); + machineSessionMapping.put(machineInfoDO.getName(), sessionId); + + log.info("机器连接成功: {}, 会话ID: {}", machineInfoDO.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); + } + } + + @Override + public void unBindingSecretKey(List SecretKeyIds) { + machineInfoMapper.unBindingSecretKey(SecretKeyIds); + } + + @VisibleForTesting + void validateMachineEnvAdd(MachineInfoVO machineInfoVO) { + if (machineInfoVO.getHostIp().isEmpty()) { + throw exception(MACHINE_INFO_HOST_IP_NULL); + } + if (machineInfoVO.getUsername().isEmpty()){ + throw exception(MACHINE_INFO_USER_NAME_NULL); + } + if(machineInfoVO.getTag().isEmpty()){ + throw exception(MACHINE_INFO_TAG_NULL); + } + + if (machineInfoVO.getAuthenticationType()!=null){ + boolean flag=true; + for (int type : AuthenticationType.ARRAYS) { + if (type == machineInfoVO.getAuthenticationType()) { + flag=false; + break; + } + if (flag){ + throw exception(MACHINE_INFO_AUTHENTICATION_TYPE_NOT_EXISTS); + } + } + }else { + throw exception(MACHINE_INFO_AUTHENTICATION_TYPE_NULL); + } + + + if (machineInfoVO.getMachineInfoType()!= null){ + boolean flag = true; + for (int type : MachineInfoType.ARRAYS) { + if (type == machineInfoVO.getMachineInfoType()) { + flag=false; + break; + } + } + if (flag) { + throw exception(MACHINE_INFO_TYPE_NOT_EXISTS); + } + }else { + throw exception(MACHINE_INFO_TYPE_NULL); + } + + } + + @VisibleForTesting + void validateMachineTagUnique(String tag){ + if (machineInfoMapper.existsByTag(tag)) { + throw exception(MACHINE_INFO_TAG_EXISTS); + } + } + @VisibleForTesting + MachineInfoDO validateMachineInfoExists(Long id) { + if (id == null) { + return null; + } + MachineInfoDO machineInfoDO = machineInfoMapper.selectById(id); + if (machineInfoDO == null) { + throw exception(MACHINE_INFO_NULL); + } + return machineInfoDO; + } + @VisibleForTesting + void validateMachineEnable(MachineInfoDO machineInfoDO) { + + if (machineInfoDO.getStatus()==MachineInfoStatus.ENABLE.getCode()){ + throw exception(MACHINE_ENABLE); + } + } + @VisibleForTesting + void validateMachineUnEnable(MachineInfoDO machineInfoDO) { + + if (machineInfoDO.getStatus()==MachineInfoStatus.UN_ENABLE.getCode()){ + throw exception(MACHINE_UN_ENABLE); + } + } + + /** + * 生成会话ID + */ + private String generateSessionId() { + return "session-" + sessionIdGenerator.incrementAndGet(); + } +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/SecretKeyServiceImpl.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/SecretKeyServiceImpl.java index 99d263b1..d0d9426f 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/SecretKeyServiceImpl.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/SecretKeyServiceImpl.java @@ -1,159 +1,157 @@ -//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 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 machineIds) { -// SecretKey secretKey = this.getById(secretKeyId); -// if (secretKey==null){ -// throw new ServiceException(ServiceException.SECRETKEY_NULL,"密钥不存在"); -// } -// List machineList = machineInfoService.listByIds(machineIds); -// machineList.forEach(machine -> machine.setSecretKeyId(secretKeyId)); -// machineInfoService.updateBatchById(machineList); -// } -// -// -// -// -// -// @Override -// public PageResult listSecretKey(SecretKeyDto secretKeyDto) { -// QueryWrapper 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 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 secretKeyIds) { -// List 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; -// } -//} +package cd.casic.module.machine.service.impl; +import cd.casic.framework.commons.pojo.PageResult; +import cd.casic.framework.commons.util.object.BeanUtils; +import cd.casic.module.machine.controller.vo.SecretKeyVO; +import cd.casic.module.machine.dal.dataobject.SecretKeyDO; +import cd.casic.module.machine.dal.mysql.SecretKeyMapper; +import cd.casic.module.machine.service.MachineInfoService; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import cd.casic.module.machine.utils.AliYunOssClient; +import cd.casic.module.machine.service.SecretKeyService; +import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; +import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; + +/** + * 密钥服务实现类 + */ +@Service("secretKeyService") +public class SecretKeyServiceImpl implements SecretKeyService { + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private AliYunOssClient aliYunOssClient; + + @Resource + private SecretKeyMapper secretKeyMapper; + + + + @Override + public SecretKeyVO getSecretKey(Long id){ + SecretKeyDO secretKeyDO = validateSecretKeyExists(id); + return BeanUtils.toBean(secretKeyDO, SecretKeyVO.class); + } + + @Override + public Long createSecretKey(SecretKeyVO secretKeyVO){ + validateSecretKeyAdd(secretKeyVO); + String ossPath = upLoadSecretKey(secretKeyVO.getPath()); + //检查得到的oss路径是否为空 + validateSecretKeyPath(ossPath); + secretKeyVO.setPath(ossPath); + SecretKeyDO secretKeyDO = BeanUtils.toBean(secretKeyVO, SecretKeyDO.class); + //todo检查密钥合法 + + secretKeyMapper.insert(secretKeyDO); + return secretKeyDO.getId(); + + + } + @Override + public void updateSecretKey(SecretKeyVO secretKeyVO) { + SecretKeyDO secretKeyDO = validateSecretKeyExists(secretKeyVO.getId()); + //如果路径改变==改变密钥 + if (!secretKeyDO.getPath().equals(secretKeyVO.getPath())) { + //todo检查密钥合法 + String ossPath = upLoadSecretKey(secretKeyVO.getPath()); + BeanUtils.copyProperties(secretKeyVO, secretKeyDO); + secretKeyDO.setPath(ossPath); + } + else { + BeanUtils.copyProperties(secretKeyVO,secretKeyDO); + } + secretKeyMapper.updateById(secretKeyDO); + } + + @Override + public void bindingMachine(Long id,List machineInfoIds) { + validateSecretKeyExists(id); + machineInfoService.bindingSecretKey(machineInfoIds, id); + } + + @Override + @Transactional + public void deleteSecretKeyList(List ids) { + ids.forEach( + secretKeyId -> { + SecretKeyDO secretKeyDO = validateSecretKeyExists(secretKeyId); + if (secretKeyDO.getPath() != null && !secretKeyDO.getPath().isEmpty()){ + try { + //文件名 + //删除子目录文件,需要在前面加上根目录文件路径 + String fileName = secretKeyDO.getPath().substring(secretKeyDO.getPath().lastIndexOf("/") + 1); + aliYunOssClient.delete(fileName); + } catch (Exception e) { + throw exception(DELETE_FILE_FAIL); + } + } + } + ); + + //绑定的机器全部设置为空 + machineInfoService.unBindingSecretKey(ids); + + secretKeyMapper.deleteBatchIds(ids); + } + + @Override + public PageResult getSecretKeypage(SecretKeyVO secretKeyVO) { + return secretKeyMapper.selectPage(secretKeyVO); + } + + 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 exception(UPLOADING_FILE_FAIL); + } + return ossPath; + } + + @VisibleForTesting + void validateSecretKeyAdd(SecretKeyVO secretKeyVO) { + if (secretKeyVO==null) { + throw exception(SECRET_KEY_NULL); + } + if (secretKeyVO.getPath().isEmpty()) { + throw exception(SECRET_KEY_PATH_NULL); + } + } + + @VisibleForTesting + void validateSecretKeyPath(String path) { + if (path.isEmpty()) { + throw exception(SECRET_KEY_PATH_NULL); + } + } + + + @VisibleForTesting + SecretKeyDO validateSecretKeyExists(Long id) { + if (id == null) { + return null; + } + SecretKeyDO secretKeyDO = secretKeyMapper.selectById(id); + if (secretKeyDO == null) { + throw exception(SECRET_KEY_NOT_EXISTS); + } + return secretKeyDO; + } + +}