diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/FileTreeComponent.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/FileTreeComponent.java index d42fa416..5e967815 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/FileTreeComponent.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/FileTreeComponent.java @@ -7,9 +7,18 @@ import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpException; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -66,8 +75,7 @@ public class FileTreeComponent { try { sftp.ls(path, selector); } catch (SftpException e) { - log.error("读取远程文件目录结构失败 [错误码: {}, 信息: {}]", - e.id, e.getMessage(), e); + log.error("读取远程文件目录结构失败, 信息: {}]", e.getMessage(), e); throw exception(READ_REMOTE_DIRECTORY_FAIL); } return new ArrayList<>(entries); @@ -211,4 +219,122 @@ public class FileTreeComponent { } return fileInfoMap; } + + // 上传到远程服务器 + public void uploadToRemoteServer(Path tempFilePath, String safeFilename, String remoteFilePath) { + // 上传文件(使用原始文件名) + try { + sftp.put(tempFilePath.toString(), remoteFilePath + safeFilename); + } catch (SftpException e) { + throw exception(UPLOAD_REMOTE_FILE_ERROR); + } finally { + // 清理临时文件 + cleanupTempFile(tempFilePath); + } + log.info("文件上传成功: {} -> {}{}", tempFilePath, remoteFilePath, safeFilename); + } + + //下载文件 + public void downloadFile(String remoteFilePath, HttpServletResponse httpServletResponse) { + InputStream inputStream = null; + OutputStream outputStream = null; + try { + // 获取文件信息并判断是否为文件夹 + SftpATTRS attrs = sftp.lstat(remoteFilePath); + if (attrs.isDir()) { + httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); + log.error("无法下载文件夹: {}", remoteFilePath); + throw exception(DOWNLOAD_FOLDER_NOT_ALLOWED); + } + + // 处理文件下载逻辑 + String fileName = Paths.get(remoteFilePath).getFileName().toString(); + + // 设置响应头 + httpServletResponse.setContentType("application/octet-stream"); + httpServletResponse.setHeader("Content-Disposition", "attachment; filename=\"" + encodeFileName(fileName) + "\""); + httpServletResponse.setContentLengthLong(attrs.getSize()); + + // 流式传输文件 + inputStream = sftp.get(remoteFilePath); + outputStream = httpServletResponse.getOutputStream(); + + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + outputStream.flush(); + } catch (SftpException e) { + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); + log.error("远程文件不存在: {}", remoteFilePath, e); + throw exception(PATH_NOT_EXISTS); + } catch (IOException e) { + httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + log.error("远程文件传输失败:{}", e.getMessage()); + throw exception(REMOTE_FILE_TRANSFER_FAIL); + } finally { + // 关闭资源 + closeQuietly(outputStream); + closeQuietly(inputStream); + } + } + + //删除远程机器文件 + public void deleteRemoteFile(String remoteFilePath) { + try { + // 检查文件是否存在 + if (checkFileExists(remoteFilePath, sftp)) { + sftp.rm(remoteFilePath); + log.info("文件删除成功: {}", remoteFilePath); + } else { + log.error("文件不存在,无法删除"); + throw exception(PATH_NOT_EXISTS); + } + } catch (SftpException e) { + log.error("删除文件时出错: {}", e.getMessage()); + throw exception(DELETE_REMOTE_FILE_ERROR); + } + } + + // 清理临时文件 + private void cleanupTempFile(Path filePath) { + if (filePath != null && Files.exists(filePath)) { + try { + Files.delete(filePath); + log.debug("临时文件已删除: {}", filePath); + } catch (IOException e) { + log.warn("临时文件删除失败: {}", filePath, e); + } + } + } + + private String encodeFileName(String fileName) { + return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + } + + + private void closeQuietly(AutoCloseable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception e) { + log.warn("关闭资源失败", e); + } + } + } + + + // 辅助方法:检查文件是否存在 + private boolean checkFileExists(String filePath, ChannelSftp sftp) { + try { + sftp.lstat(filePath); + return true; + } catch (SftpException e) { + if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) { + return false; + } + } + return true; + } } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketConnection.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketConnection.java index 1a0b6770..16b4d7c2 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketConnection.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketConnection.java @@ -1,230 +1,220 @@ -//package cd.casic.module.machine.component; -// -//import cd.casic.framework.commons.exception.ServiceException; -//import cd.casic.module.machine.dal.dataobject.MachineInfoDO; -//import cd.casic.module.machine.enums.AuthenticationType; -//import cd.casic.module.machine.enums.ConnectionStatus; -//import cd.casic.module.machine.enums.SSHChanelType; -//import cd.casic.module.machine.service.SecretKeyService; -//import com.jcraft.jsch.Channel; -//import com.jcraft.jsch.ChannelExec; -//import com.jcraft.jsch.JSch; -//import com.jcraft.jsch.JSchException; -//import com.jcraft.jsch.Session; -//import lombok.Data; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.stereotype.Component; -//import org.springframework.util.StringUtils; -//import org.springframework.web.socket.TextMessage; -//import org.springframework.web.socket.WebSocketSession; -// -//import java.io.BufferedReader; -//import java.io.IOException; -//import java.io.InputStream; -//import java.io.InputStreamReader; -//import java.nio.charset.StandardCharsets; -//import java.util.Properties; -// -//import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; -//import static cd.casic.module.machine.contants.MachineErrorCodeConstants.SECRET_KEY_NULL; -//import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; -// -//@Slf4j -//@Data -//@Component -//public class WebSocketConnection { -// private SecretKeyService secretKeyService; -// -// private MachineInfoDO machineInfo; -// private ConnectionStatus connectionStatus = ConnectionStatus.DISCONNECTED; -// private Session sshSession; -// -// public WebSocketConnection(SecretKeyService secretKeyService) { -// this.secretKeyService = secretKeyService; -// } -// -// private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒) -// -// public void initConnection(MachineInfoDO machineInfo) { -// try { -// this.machineInfo = machineInfo; -// this.sshSession = doConnect(machineInfo); -// log.info("已成功建立 SSH 连接至 {} ", machineInfo.getHostIp()); -// this.connectionStatus = ConnectionStatus.CONNECTING; -// } catch (ServiceException e) { -// log.warn("SSH 连接失败: {}", e.getMessage()); -// throw e; -// } -// } -// -// public void disconnect() { -// if (sshSession != null && sshSession.isConnected()) { -// try { -// sshSession.disconnect(); -// log.info("SSH连接关闭: {}", machineInfo.getHostIp()); -// } catch (Exception e) { -// log.error("关闭SSH连接失败: {}", e.getMessage()); -// throw exception(CLOSE_CLOSE_SESSION_ERROR); -// } -// } -// connectionStatus = ConnectionStatus.DISCONNECTED; -// } -// -// /** -// * 执行远程命令,支持超时和中断处理 -// */ -// public void executeCommand(WebSocketSession webSocketSession, String command) { -// // 1. 检查连接状态 -// if (sshSession == null || !sshSession.isConnected()) { -// sendErrorMessage(webSocketSession, "SSH连接未建立或已断开"); -// return; -// } -// try { -// // 2. 创建SSH命令执行通道 -// Channel channel; -// try { -// channel = sshSession.openChannel(SSHChanelType.EXEC.getMessage()); -// } catch (JSchException e) { -// throw exception(CREATE_CHANEL_ERROR); -// } -// ((ChannelExec) channel).setCommand(command); -// // 3. 设置输入/输出流 -// channel.setInputStream(null); -// ((ChannelExec) channel).setErrStream(System.err); -// // 4. 获取命令输出流 -// InputStream inputStream = channel.getInputStream(); -// InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); -// BufferedReader reader = new BufferedReader(inputStreamReader); -// // 5. 连接并执行命令 -// channel.connect(); -// // 6. 读取命令输出并实时发送给客户端 -// String line; -// while ((line = reader.readLine()) != null) { -// // 实时发送输出到客户端 -// webSocketSession.sendMessage(new TextMessage(line)); -// } -// // 7. 等待命令执行完成 -// int exitStatus = channel.getExitStatus(); -// // 8. 发送命令执行完毕的消息 -// webSocketSession.sendMessage(new TextMessage( -// "[系统] 命令执行完毕,退出状态: " + exitStatus -// )); -// // 9. 关闭通道 -// channel.disconnect(); -// } catch (JSchException | IOException e) { -// throw exception(EXECUTE_COMMAND_FAIL); -// } -// } -// -// // 发送错误消息的辅助方法 -// public void sendErrorMessage(WebSocketSession webSocketSession, String message) { -// try { -// if (webSocketSession.isOpen()) { -// webSocketSession.sendMessage(new TextMessage("[错误] " + message)); -// } -// } catch (IOException e) { -// log.error("发送错误消息失败", e); -// throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); -// } -// } -// -// /** -// * 实际执行连接逻辑 -// */ -// private Session doConnect(MachineInfoDO machineInfo) { -// JSch jsch = new JSch(); -// // 配置认证方式 -// configureAuthentication(jsch, machineInfo); -// Session session; -// // 创建SSH会话 -// try { -// session = jsch.getSession( -// machineInfo.getUsername(), -// machineInfo.getHostIp(), -// machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22 -// ); -// } catch (JSchException e) { -// throw exception(CREATE_SESSION_ERROR); -// } -// // 配置连接参数 -// configureSession(session, machineInfo); -// // 建立连接 -// try { -// session.connect(CONNECTION_TIMEOUT); -// } catch (JSchException e) { -// throw exception(SESSION_CONNECT_ERROR); -// } -// return session; -// } -// -//// /** -//// * 配置认证方式(密码或密钥) -//// */ -//// private void configureAuthentication(JSch jsch, MachineInfoDO machineInfo) { -//// if (machineInfo.getAuthenticationType() == AuthenticationType.SECRET_KEY.getCode()) { -//// // 密钥认证 -//// if (machineInfo.getSecretKeyId() == null) { -//// throw exception(SECRET_KEY_NULL); -//// } -//// String pubKeyContent = secretKeyService.getKeyContent(machineInfo.getSecretKeyId()); -//// // 验证秘钥格式 -//// if (!pubKeyContent.startsWith("-----BEGIN")) { -//// log.error("无效的密钥格式{}", pubKeyContent); -//// throw exception(INVALID_kEY_FORMAT); -//// } -//// try { -//// // 尝试加载秘钥私钥 -//// jsch.addIdentity( -//// machineInfo.getName(), -//// pubKeyContent.getBytes(StandardCharsets.UTF_8), -//// null, -//// null -//// ); -//// log.info("密钥加载成功 {}", machineInfo.getHostIp()); -//// } catch (JSchException e) { -//// log.error("密钥加载失败: {}", e.getMessage()); -//// throw exception(READ_SECRET_CONTENT_ERROR); -//// } -//// } else if (machineInfo.getAuthenticationType() == AuthenticationType.PASSWORD.getCode()) { -//// // 密码认证 -//// if (!StringUtils.hasText(machineInfo.getPassword())) { -//// throw exception(PASSWORD_NOT_EXISTS); -//// } -//// } else { -//// log.error("不支持该验证类型:{}", machineInfo.getAuthenticationType()); -//// throw exception(NOT_SUPPORT_AUTHENTICATION_TYPE); -//// } -//// } -// -// /** -// * 配置SSH会话参数(安全增强) -// */ -// private void configureSession(Session session, MachineInfoDO machineInfo) { -// Properties config = new Properties(); -// // 根据认证类型配置不同的认证策略 -// if (machineInfo.getAuthenticationType() == 1) { // 密码认证 -// // 设置密码 -// session.setPassword(machineInfo.getPassword()); -// config.put("StrictHostKeyChecking", "no"); -// // 仅使用密码认证(禁用其他认证方式) -// config.put("PreferredAuthentications", "password"); -// // 禁用公钥相关配置(避免干扰) -// config.put("PubkeyAuthentication", "no"); -// } else { // 密钥认证 -// try { -// String preKeyPath = secretKeyService.getSecretKey(machineInfo.getSecretKeyId()).getPath(); -// JSch jsch = new JSch(); -// jsch.addIdentity(preKeyPath); // 添加私钥 -// // 保持默认认证顺序(公钥优先) -// config.put("PreferredAuthentications", "publicKey,password,keyboard-interactive"); -// } catch (JSchException e) { -// log.error("SSH密钥配置失败", e); -// throw exception(SSH_KEY_CONFIGURATION_FAIL); -// } -// } -// config.put("ServerAliveInterval", "30"); // 每30秒发送一次心跳 -// config.put("ServerAliveCountMax", "3"); // 允许3次心跳失败 -// session.setConfig(config); -// } -// -//} +package cd.casic.module.machine.component; + +import cd.casic.framework.commons.exception.ServiceException; +import cd.casic.module.machine.dal.dataobject.MachineInfoDO; +import cd.casic.module.machine.enums.AuthenticationType; +import cd.casic.module.machine.enums.ConnectionStatus; +import cd.casic.module.machine.enums.SSHChanelType; +import cd.casic.module.machine.service.SecretKeyService; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; +import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; + +@Slf4j +@Data +@Component +public class WebSocketConnection { + private SecretKeyService secretKeyService; + private MachineInfoDO machineInfo; + private ConnectionStatus connectionStatus = ConnectionStatus.DISCONNECTED; + private Session sshSession; + + public WebSocketConnection(SecretKeyService secretKeyService) { + this.secretKeyService = secretKeyService; + } + + private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒) + + public void initConnection(MachineInfoDO machineInfo) { + try { + this.machineInfo = machineInfo; + this.sshSession = doConnect(machineInfo); + log.info("已成功建立 SSH 连接至 {} ", machineInfo.getHostIp()); + this.connectionStatus = ConnectionStatus.CONNECTING; + } catch (ServiceException e) { + log.warn("SSH 连接失败: {}", e.getMessage()); + throw e; + } + } + + public void disconnect() { + if (sshSession != null && sshSession.isConnected()) { + try { + sshSession.disconnect(); + log.info("SSH连接关闭: {}", machineInfo.getHostIp()); + } catch (Exception e) { + log.error("关闭SSH连接失败: {}", e.getMessage()); + throw exception(CLOSE_CLOSE_SESSION_ERROR); + } + } + connectionStatus = ConnectionStatus.DISCONNECTED; + } + + /** + * 执行远程命令,支持超时和中断处理 + */ + public void executeCommand(WebSocketSession webSocketSession, String command) { + // 1. 检查连接状态 + if (sshSession == null || !sshSession.isConnected()) { + sendErrorMessage(webSocketSession, "SSH连接未建立或已断开"); + return; + } + try { + // 2. 创建SSH命令执行通道 + Channel channel; + try { + channel = sshSession.openChannel(SSHChanelType.EXEC.getMessage()); + } catch (JSchException e) { + throw exception(CREATE_CHANEL_ERROR); + } + ((ChannelExec) channel).setCommand(command); + // 3. 设置输入/输出流 + channel.setInputStream(null); + ((ChannelExec) channel).setErrStream(System.err); + // 4. 获取命令输出流 + InputStream inputStream = channel.getInputStream(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + BufferedReader reader = new BufferedReader(inputStreamReader); + // 5. 连接并执行命令 + channel.connect(); + // 6. 读取命令输出并实时发送给客户端 + String line; + while ((line = reader.readLine()) != null) { + // 实时发送输出到客户端 + webSocketSession.sendMessage(new TextMessage(line)); + } + // 7. 等待命令执行完成 + int exitStatus = channel.getExitStatus(); + // 8. 发送命令执行完毕的消息 + webSocketSession.sendMessage(new TextMessage( + "[系统] 命令执行完毕,退出状态: " + exitStatus + )); + // 9. 关闭通道 + channel.disconnect(); + } catch (JSchException | IOException e) { + throw exception(EXECUTE_COMMAND_FAIL); + } + } + + // 发送错误消息的辅助方法 + public void sendErrorMessage(WebSocketSession webSocketSession, String message) { + try { + if (webSocketSession.isOpen()) { + webSocketSession.sendMessage(new TextMessage("[错误] " + message)); + } + } catch (IOException e) { + log.error("发送错误消息失败", e); + throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); + } + } + + /** + * 实际执行连接逻辑 + */ + private Session doConnect(MachineInfoDO machineInfo) { + JSch jsch = new JSch(); + // 配置认证方式 + configureAuthentication(jsch, machineInfo); + Session session; + // 创建SSH会话 + try { + session = jsch.getSession( + machineInfo.getUsername(), + machineInfo.getHostIp(), + machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22 + ); + } catch (JSchException e) { + throw exception(CREATE_SESSION_ERROR); + } + // 配置连接参数 + configureSession(session, machineInfo); + // 建立连接 + try { + session.connect(CONNECTION_TIMEOUT); + } catch (JSchException e) { + throw exception(SESSION_CONNECT_ERROR); + } + return session; + } + + /** + * 配置认证方式(密码或密钥) + */ + private void configureAuthentication(JSch jsch, MachineInfoDO machineInfo) { + if (machineInfo.getAuthenticationType() == AuthenticationType.SECRET_KEY.getCode()) { + // 密钥认证 + if (machineInfo.getSecretKeyId() == null) { + throw exception(SECRET_KEY_NULL); + } + String pubKeyContent = secretKeyService.getPublicKeyContent(machineInfo.getSecretKeyId()); + // 验证秘钥格式 + if (!pubKeyContent.startsWith("-----BEGIN")) { + log.error("无效的密钥格式{}", pubKeyContent); + throw exception(INVALID_kEY_FORMAT); + } + try { + // 尝试加载秘钥私钥 + jsch.addIdentity( + machineInfo.getName(), + pubKeyContent.getBytes(StandardCharsets.UTF_8), + null, + null + ); + log.info("密钥加载成功 {}", machineInfo.getHostIp()); + } catch (JSchException e) { + log.error("密钥加载失败: {}", e.getMessage()); + throw exception(READ_SECRET_CONTENT_ERROR); + } + } else if (machineInfo.getAuthenticationType() == AuthenticationType.PASSWORD.getCode()) { + // 密码认证 + if (!StringUtils.hasText(machineInfo.getPassword())) { + throw exception(PASSWORD_NOT_EXISTS); + } + } else { + log.error("不支持该验证类型:{}", machineInfo.getAuthenticationType()); + throw exception(NOT_SUPPORT_AUTHENTICATION_TYPE); + } + } + + /** + * 配置SSH会话参数(安全增强) + */ + private void configureSession(Session session, MachineInfoDO machineInfo) { + Properties config = new Properties(); + // 根据认证类型配置不同的认证策略 + if (machineInfo.getAuthenticationType() == 1) { // 密码认证 + // 设置密码 + session.setPassword(machineInfo.getPassword()); + config.put("StrictHostKeyChecking", "no"); + // 仅使用密码认证(禁用其他认证方式) + config.put("PreferredAuthentications", "password"); + // 禁用公钥相关配置(避免干扰) + config.put("PubkeyAuthentication", "no"); + } else { // 密钥认证 + // 保持默认认证顺序(公钥优先) + config.put("PreferredAuthentications", "publicKey,password,keyboard-interactive"); + } + config.put("ServerAliveInterval", "30"); // 每30秒发送一次心跳 + config.put("ServerAliveCountMax", "3"); // 允许3次心跳失败 + session.setConfig(config); + } + +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketSessionManager.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketSessionManager.java index 88c7acad..24e17f0b 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketSessionManager.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketSessionManager.java @@ -1,114 +1,70 @@ -//package cd.casic.module.machine.component; -// -//import org.springframework.stereotype.Component; -//import org.springframework.web.socket.WebSocketSession; -// -//import java.util.Collection; -//import java.util.concurrent.ConcurrentHashMap; -// -//@Component("machineWebSocketSessionManger") -////管理webSocketSession -//public class WebSocketSessionManager { -// -// //webSocketSessionId - WebSocketSession 保存 WebSocketSession 对象与会话 ID 的映射 -// private static final ConcurrentHashMap WebSocketSessionMap = new ConcurrentHashMap<>(); -// -// //webSocketSessionId - WebSocketConnection 与远程机器的会话管理 -// private static final ConcurrentHashMap sessionConnectionMap = new ConcurrentHashMap<>(); -// -// //机器id - WebSocketConnection -// private static final ConcurrentHashMap webSocketSessionConnectionMap = new ConcurrentHashMap<>(); -// -// public static void addWebSocketSession(String sessionId, WebSocketSession session) { -// WebSocketSessionMap.put(sessionId, session); -// } -// -// /** -// * 获取 WebSocketSession -// */ -// public static WebSocketSession getWebSocketSession(String sessionId) { -// return WebSocketSessionMap.get(sessionId); -// } -// -// /** -// * 移除 WebSocketSession -// */ -// public static void removeWebSocketSession(String sessionId) { -// WebSocketSessionMap.remove(sessionId); -// } -// -// /** -// * 检查 sessionId 是否存在 -// */ -// public static boolean containsWebSocketSession(String sessionId) { -// return WebSocketSessionMap.containsKey(sessionId); -// } -// -// /** -// * 获取所有 WebSocketSession -// */ -// public static Collection getAllWebSocketSessions() { -// return WebSocketSessionMap.values(); -// } -// -// public static void addWebSocketConnection(String sessionId, WebSocketConnection connection) { -// sessionConnectionMap.put(sessionId, connection); -// } -// -// /** -// * 获取 WebSocketConnection -// */ -// public static WebSocketConnection getWebSocketConnection(String sessionId) { -// return sessionConnectionMap.get(sessionId); -// } -// -// /** -// * 移除 WebSocketConnection -// */ -// public static void removeWebSocketConnection(String sessionId) { -// sessionConnectionMap.remove(sessionId); -// } -// -// /** -// * 检查 sessionId 是否存在 -// */ -// public static boolean containsWebSocketConnection(String sessionId) { -// return sessionConnectionMap.containsKey(sessionId); -// } -// -// /** -// * 获取所有 WebSocketConnection -// */ -// public static ConcurrentHashMap getAllWebSocketConnections() { -// return webSocketSessionConnectionMap; -// } -// -// /** -// * 添加 WebSocketConnection -// */ -// public static void addWebSocketConnectionByMachineId(Long machineId, WebSocketConnection connection) { -// webSocketSessionConnectionMap.put(machineId, connection); -// } -// -// /** -// * 获取 WebSocketConnection -// */ -// public static WebSocketConnection getWebSocketConnectionByMachineId(Long machineId) { -// return webSocketSessionConnectionMap.get(machineId); -// } -// -// /** -// * 移除 WebSocketConnection -// */ -// public static void removeWebSocketConnectionByMachineId(Long machineId) { -// webSocketSessionConnectionMap.remove(machineId); -// } -// -// /** -// * 检查 machineId 是否存在 -// */ -// public static boolean containsMachineId(Long machineId) { -// return webSocketSessionConnectionMap.containsKey(machineId); -// } -// -//} +package cd.casic.module.machine.component; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.ConcurrentHashMap; + +@Component("machineWebSocketSessionManger") +//管理webSocketSession +public class WebSocketSessionManager { + + //webSocketSessionId - WebSocketConnection 与远程机器的会话管理 + private static final ConcurrentHashMap sessionConnectionMap = new ConcurrentHashMap<>(); + + //机器id - WebSocketConnection + private static final ConcurrentHashMap webSocketSessionConnectionMap = new ConcurrentHashMap<>(); + + public static void addWebSocketConnection(String sessionId, WebSocketConnection connection) { + sessionConnectionMap.put(sessionId, connection); + } + + /** + * 获取 WebSocketConnection + */ + public static WebSocketConnection getWebSocketConnection(String sessionId) { + return sessionConnectionMap.get(sessionId); + } + + /** + * 移除 WebSocketConnection + */ + public static void removeWebSocketConnection(String sessionId) { + sessionConnectionMap.remove(sessionId); + } + + /** + * 获取所有 WebSocketConnection + */ + public static ConcurrentHashMap getAllWebSocketConnections() { + return webSocketSessionConnectionMap; + } + + /** + * 添加 WebSocketConnection + */ + public static void addWebSocketConnectionByMachineId(Long machineId, WebSocketConnection connection) { + webSocketSessionConnectionMap.put(machineId, connection); + } + + /** + * 获取 WebSocketConnection + */ + public static WebSocketConnection getWebSocketConnectionByMachineId(Long machineId) { + return webSocketSessionConnectionMap.get(machineId); + } + + /** + * 移除 WebSocketConnection + */ + public static void removeWebSocketConnectionByMachineId(Long machineId) { + webSocketSessionConnectionMap.remove(machineId); + } + + /** + * 检查 machineId 是否存在 + */ + public static boolean containsMachineId(Long machineId) { + return webSocketSessionConnectionMap.containsKey(machineId); + } + +} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/WebSocketConfig.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/WebSocketConfig.java index 00b84285..c44d5d9e 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/WebSocketConfig.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/WebSocketConfig.java @@ -1,40 +1,40 @@ -//package cd.casic.module.machine.configuration; -// -//import cd.casic.module.machine.Interceptor.WebSocketHandshakeInterceptor; -//import cd.casic.module.machine.handler.MachineWebSocketHandler; -//import jakarta.annotation.Resource; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.web.socket.config.annotation.EnableWebSocket; -//import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -//import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; -//import org.springframework.web.socket.server.standard.ServerEndpointExporter; -//import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; -// -//@Configuration -//@EnableWebSocket -////WebSocket端点配置 -//public class WebSocketConfig implements WebSocketConfigurer { -// @Resource -// private MachineWebSocketHandler machineWebSocketHandler; -// -// @Resource -// private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor; -// -// @Override -// public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { -// registry.addHandler(machineWebSocketHandler, "/ssh/terminal") -// .addInterceptors(webSocketHandshakeInterceptor) -// .setAllowedOrigins("*"); // 允许跨域(生产环境需限制) -// } -// -// @Bean -// public ServletServerContainerFactoryBean createWebSocketContainer() { -// return new ServletServerContainerFactoryBean(); -// } -// -// @Bean -// public ServerEndpointExporter serverEndpointExporter() { -// return new ServerEndpointExporter(); -// } -//} +package cd.casic.module.machine.configuration; + +import cd.casic.module.machine.Interceptor.WebSocketHandshakeInterceptor; +import cd.casic.module.machine.handler.MachineWebSocketHandler; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +@Configuration +@EnableWebSocket +//WebSocket端点配置 +public class WebSocketConfig implements WebSocketConfigurer { + @Resource + private MachineWebSocketHandler machineWebSocketHandler; + + @Resource + private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(machineWebSocketHandler, "/ssh/terminal") + .addInterceptors(webSocketHandshakeInterceptor) + .setAllowedOrigins("*"); // 允许跨域(生产环境需限制) + } + + @Bean + public ServletServerContainerFactoryBean createWebSocketContainer() { + return new ServletServerContainerFactoryBean(); + } + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} 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 2d14d8e1..8d32153b 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 @@ -19,13 +19,6 @@ public interface MachineErrorCodeConstants { ErrorCode MACHINE_ENABLE = new ErrorCode(1_003_000_009, "机器启用中"); ErrorCode MACHINE_UN_ENABLE = new ErrorCode(1_003_000_010, "机器不可用"); - // ========== 文件操作模块 1-003-001-000 ========== - ErrorCode UPLOADING_FILE_FAIL = new ErrorCode(1_003_001_000, "上传文件失败"); - ErrorCode DOWNLOAD_FILE_FAIL = new ErrorCode(1_003_001_001, "下载失败"); - ErrorCode FILENAME_NULL = new ErrorCode(1_003_001_002, "文件名为空"); - ErrorCode READ_FILE_FAIL = new ErrorCode(1_003_001_003, "读取文件失败"); - ErrorCode DELETE_FILE_FAIL = new ErrorCode(1_003_001_004, "删除文件失败"); - // ========== 机器环境变量模块 1-003-002-000 ========== ErrorCode MACHINE_ENV_NULL = new ErrorCode(1_003_002_000, "机器环境变量为空"); ErrorCode MACHINE_ENV_NOT_EXISTS = new ErrorCode(1_003_002_001, "机器不存在"); @@ -41,28 +34,20 @@ public interface MachineErrorCodeConstants { // ========== 密钥模块 1-003-004-000 ========== ErrorCode SECRET_KEY_NULL = new ErrorCode(1_003_004_000, "密钥为空"); ErrorCode SECRET_KEY_NOT_EXISTS = new ErrorCode(1_003_004_001, "密钥不存在"); - ErrorCode SECRET_KEY_PATH_NULL = new ErrorCode(1_003_004_002, "密钥路径为空"); - ErrorCode INVALID_kEY_FORMAT = new ErrorCode(1_003_004_003, "无效的密钥格式"); - ErrorCode READ_SECRET_CONTENT_ERROR = new ErrorCode(1_003_004_004, "读取密钥加载失败"); - - // ========== 其他模块 1-003-005-000 ========== - ErrorCode OSS_PARAM_NULL = new ErrorCode(1_003_005_000, "oss参数无法读取"); - ErrorCode FILE_UPLOAD_ERROR = new ErrorCode(1_003_005_001, "文件上传失败"); - ErrorCode FILE_DOWNLOAD_ERROR = new ErrorCode(1_003_005_002, "文件下载失败"); + ErrorCode INVALID_kEY_FORMAT = new ErrorCode(1_003_004_002, "无效的密钥格式"); + ErrorCode READ_SECRET_CONTENT_ERROR = new ErrorCode(1_003_004_003, "读取密钥加载失败"); //========== 会话连接模块 1-003-006-000 ========== ErrorCode SESSION_CONNECT_ERROR = new ErrorCode(1_003_006_001, "会话连接失败"); ErrorCode CLOSE_CLOSE_SESSION_ERROR = new ErrorCode(1_003_006_002, "关闭连接失败"); - ErrorCode SESSION_NOT_CONNECT = new ErrorCode(1_003_006_003, "会话未连接"); - ErrorCode EXECUTE_COMMAND_FAIL = new ErrorCode(1_003_006_004, "命令执行失败"); - ErrorCode PASSWORD_NOT_EXISTS = new ErrorCode(1_003_006_005, "密码不存在"); - ErrorCode SSH_KEY_CONFIGURATION_FAIL = new ErrorCode(1_003_006_006, "SSH密钥配置失败"); - ErrorCode NOT_SUPPORT_AUTHENTICATION_TYPE = new ErrorCode(1_003_006_007, "认证类型不支持"); - ErrorCode CREATE_SESSION_ERROR = new ErrorCode(1_003_006_008, "创建会话失败"); - ErrorCode WEBSOCKET_SEND_MESSAGE_ERROR = new ErrorCode(1_003_006_009, "websocket发送消息失败"); - ErrorCode CREATE_CHANEL_ERROR = new ErrorCode(1_003_006_010, "执行通道创建失败"); - ErrorCode CHANEL_CONNECT_FAIL = new ErrorCode(1_003_006_011, "通道连接失败"); - ErrorCode FAILED_TO_PARSE_URL_PARAMETERS = new ErrorCode(1_003_006_012, "解析URL参数失败"); + ErrorCode EXECUTE_COMMAND_FAIL = new ErrorCode(1_003_006_003, "命令执行失败"); + ErrorCode PASSWORD_NOT_EXISTS = new ErrorCode(1_003_006_004, "密码不存在"); + ErrorCode NOT_SUPPORT_AUTHENTICATION_TYPE = new ErrorCode(1_003_006_005, "认证类型不支持"); + ErrorCode CREATE_SESSION_ERROR = new ErrorCode(1_003_006_006, "创建会话失败"); + ErrorCode WEBSOCKET_SEND_MESSAGE_ERROR = new ErrorCode(1_003_006_007, "websocket发送消息失败"); + ErrorCode CREATE_CHANEL_ERROR = new ErrorCode(1_003_006_008, "执行通道创建失败"); + ErrorCode CHANEL_CONNECT_FAIL = new ErrorCode(1_003_006_009, "通道连接失败"); + ErrorCode FAILED_TO_PARSE_URL_PARAMETERS = new ErrorCode(1_003_006_010, "解析URL参数失败"); //========== 远程文件树模块 1-003-007-000 ========== ErrorCode NOT_DIRECTORY_NODE = new ErrorCode(1_003_007_001, "非目录节点不能添加子节点"); @@ -70,4 +55,10 @@ public interface MachineErrorCodeConstants { ErrorCode CONNECTION_LOST = new ErrorCode(1_003_007_003, "SFTP连接无效,无法列出文件"); ErrorCode PATH_NOT_EXISTS = new ErrorCode(1_003_007_004, "路径不存在"); ErrorCode NO_PATH_PERMISSION = new ErrorCode(1_003_007_005, "无路径访问权限"); + ErrorCode INVALID_FILE_NAME = new ErrorCode(1_003_007_006, "无效的文件名"); + ErrorCode CREATE_TEMP_FILE_ERROR = new ErrorCode(1_003_007_007, "创建临时文件失败"); + ErrorCode UPLOAD_REMOTE_FILE_ERROR = new ErrorCode(1_003_007_008, "上传文件到远程机器出错"); + ErrorCode DOWNLOAD_FOLDER_NOT_ALLOWED = new ErrorCode(1_003_007_009, "无法下载文件夹"); + ErrorCode REMOTE_FILE_TRANSFER_FAIL = new ErrorCode(1_003_007_010, "远程文件传输失败"); + ErrorCode DELETE_REMOTE_FILE_ERROR = new ErrorCode(1_003_007_011, "删除文件时出错"); } 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 d3f66d82..a6cfd928 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 @@ -3,6 +3,7 @@ package cd.casic.module.machine.controller; import cd.casic.framework.commons.pojo.CommonResult; import cd.casic.framework.commons.pojo.PageResult; import cd.casic.framework.commons.util.object.BeanUtils; +import cd.casic.module.machine.controller.vo.SecretKeyVO; import cd.casic.module.machine.dal.dataobject.MachineInfoDO; import cd.casic.module.machine.enums.ConnectionStatus; import cd.casic.module.machine.service.MachineInfoService; @@ -11,9 +12,11 @@ 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.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.Map; @@ -35,7 +38,6 @@ public class MachineInfoController { return success(id); } - @PutMapping("/update") @Operation(summary = "编辑机器信息") // @PreAuthorize("@ss.hasPermission('ci:machineInfo:update')") @@ -78,54 +80,60 @@ public class MachineInfoController { return success(true); } -// @GetMapping("/test") -// @Operation(summary = "测试机器连接") -// public CommonResult testConnection(@RequestParam("id") Long id) { -// return success(machineInfoService.testConnection(id)); -// } -// -// @GetMapping("/status") -// @Operation(summary = "获取机器连接状态") -// public CommonResult getConnectionStatus(@RequestParam Long id) { -// return success(machineInfoService.getConnectionStatus(id)); -// } -// -// @GetMapping("/status/all") -// @Operation(summary = "获取所有机器连接状态") -// public CommonResult> getAllConnectionStatus() { -// return success(machineInfoService.getAllConnectionStatus()); -// } -// -// @GetMapping("/connect") -// @Operation(summary = "建立连接") -// public CommonResult> connect(@RequestParam Long id) { -// return success(machineInfoService.connect(id)); -// } + @PutMapping("/bindingSecretKey") + @Operation(summary = "绑定/解绑密钥") + public void bindingSecretKey(@RequestBody SecretKeyVO secretKeyVO) { + machineInfoService.bindingSecretKey(secretKeyVO); + } -// @GetMapping("/fileTreeNode") -// @Operation(summary = "获得文件树") -// public CommonResult> fileTreeNode( -// @RequestParam Long machineId, -// @RequestParam(required = false, defaultValue = "/") String path -// ) { -// return CommonResult.success(machineInfoService.fileTreeNode(machineId, path)); -// } -// @GetMapping("/upload") -// @Operation(summary = "上传文件到远程机器") -// public CommonResult uploadFile( -// @RequestParam String sessionId, -// @RequestParam String localFilePath, -// @RequestParam String remoteFilePath -// ) { -// return success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath)); -// } -// -// @GetMapping("/download") -// @Operation(summary = "从远程机器下载文件") -// public CommonResult downloadFile( -// @RequestParam String sessionId, -// @RequestParam String remoteFilePath, -// @RequestParam String localFilePath) { -// return success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath)); -// } + @GetMapping("/test") + @Operation(summary = "测试机器连接") + public CommonResult testConnection(@RequestParam("id") Long id) { + return success(machineInfoService.testConnection(id)); + } + + @GetMapping("/status") + @Operation(summary = "获取机器连接状态") + public CommonResult getConnectionStatus(@RequestParam Long id) { + return success(machineInfoService.getConnectionStatus(id)); + } + + @GetMapping("/status/all") + @Operation(summary = "获取所有机器连接状态") + public CommonResult> getAllConnectionStatus() { + return success(machineInfoService.getAllConnectionStatus()); + } + + @GetMapping("/connect") + @Operation(summary = "建立连接") + public CommonResult> connect(@RequestParam Long id) { + return success(machineInfoService.connect(id)); + } + + @GetMapping("/fileTreeNode") + @Operation(summary = "获得文件树") + public CommonResult> fileTreeNode( + @RequestParam Long machineId, + @RequestParam(required = false, defaultValue = "/") String path + ) { + return success(machineInfoService.fileTreeNode(machineId, path)); + } + + @PostMapping("/upload") + @Operation(summary = "上传文件到远程机器") + public CommonResult uploadFile(@RequestParam MultipartFile file, @RequestParam String remoteFilePath) { + return success(machineInfoService.uploadFile(file, remoteFilePath)); + } + + @GetMapping("/download") + @Operation(summary = "从远程机器下载文件") + public CommonResult downloadFile(@RequestParam String remoteFilePath, HttpServletResponse httpServletResponse) { + return success(machineInfoService.downloadFile(remoteFilePath, httpServletResponse)); + } + + @DeleteMapping("/deleteRemoteFile") + @Operation(summary = "删除远程机器选定文件") + public CommonResult deleteRemoteFile(@RequestParam String remoteFilePath) { + return success(machineInfoService.deleteRemoteFile(remoteFilePath)); + } } 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 bf1e9b7a..1624b1c2 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 @@ -12,9 +12,6 @@ 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.core.io.InputStreamResource; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -33,7 +30,7 @@ public class SecretKeyController { @PostMapping(value = "/create") @Operation(summary = "新增密钥") // @PreAuthorize("@ss.hasPermission('ci:secretKey:create')") - public CommonResult createSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) throws Exception { + public CommonResult createSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) { Long secretKeyId = secretKeyService.createSecretKey(secretKeyVO); return success(secretKeyId); } @@ -46,21 +43,6 @@ public class SecretKeyController { return success(true); } - @PutMapping("/bindingMachine") - @Operation(summary = "绑定机器") -// @PreAuthorize("@ss.hasPermission('ci:secretKey:binding')") - public CommonResult bindingMachine(@Valid @RequestBody SecretKeyVO secretKeyVO) { - secretKeyService.bindingMachine(secretKeyVO); - return success(true); - } - - @PutMapping("/unbindMachine") - @Operation(summary = "解绑机器") - public CommonResult unbindMachine(@Valid @RequestBody SecretKeyVO secretKeyVO) { - secretKeyService.unbindMachine(secretKeyVO); - return success(true); - } - @GetMapping("/getBindMachine") @Operation(summary = "获取密钥绑定的机器列表") public CommonResult> getBindMachine(@RequestParam Long secretKeyId) { @@ -74,13 +56,6 @@ public class SecretKeyController { return success(secretKeyVO); } - @DeleteMapping("/delete") - @Operation(summary = "密钥信息删除") - public CommonResult delete(@RequestParam("id") Long id) { - secretKeyService.deleteSecretKey(id); - return success(true); - } - @DeleteMapping("/deleteList") @Operation(summary = "批量删除密钥") // @PreAuthorize("@ss.hasPermission('ci:secretKey:delete')") @@ -92,16 +67,10 @@ public class SecretKeyController { @PostMapping("/list") @Operation(summary = "获取密钥信息列表") public CommonResult> getSecretKeyPage(@Valid @RequestBody SecretKeyVO secretKeyVO) { - PageResult pageResult = secretKeyService.getSecretKeypage(secretKeyVO); + PageResult pageResult = secretKeyService.getSecretKeyPage(secretKeyVO); if (CollUtil.isEmpty(pageResult.getList())) { return success(new PageResult<>(pageResult.getTotal())); } return success(BeanUtils.toBean(pageResult, SecretKeyVO.class)); } - -// @GetMapping("/download") -// @Operation(summary = "下载密钥文件") -// public ResponseEntity downloadSecretFile(@RequestParam("id") Long id) { -// return secretKeyService.downloadSecretFile(id); -// } } 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 index 041780f6..211f9ce9 100644 --- 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 @@ -45,4 +45,6 @@ public class SecretKeyVO extends PageParam { @Schema(description = "公钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "******") private String public_key; + @Schema(description = "绑定/解绑密钥",example = "true") + private Boolean enableBind; } 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 985a5a81..1502563d 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 @@ -28,10 +28,14 @@ public interface MachineInfoMapper extends BaseMapperX { this.update(null, set); } - default void bindingSecretKey(List machineInfoIds, Long secretKeyId) { + default void bindingSecretKey(List machineInfoIds, Long secretKeyId,Boolean enableBind) { UpdateWrapper wrapper = new UpdateWrapper<>(); - wrapper.in("id", machineInfoIds) // 匹配 ID 集合 - .set("secret_key_id", secretKeyId); // 设置新的 status 值 + wrapper.in("id", machineInfoIds); + if (enableBind){ + wrapper.set("secret_key_id", secretKeyId);// 匹配 ID 集合 + }else { + wrapper.set("secret_key_id", null); + } this.update(null, wrapper); } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java index ba6a0f6f..42a18762 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java @@ -1,67 +1,64 @@ -//package cd.casic.module.machine.handler; -// -////import cd.casic.module.machine.component.WebSocketConnection; -////import cd.casic.module.machine.component.WebSocketSessionManager; -//import cd.casic.module.machine.enums.ConnectionStatus; -//import org.jetbrains.annotations.NotNull; -//import org.springframework.stereotype.Component; -//import org.springframework.web.socket.CloseStatus; -//import org.springframework.web.socket.TextMessage; -//import org.springframework.web.socket.WebSocketSession; -//import org.springframework.web.socket.handler.TextWebSocketHandler; -// -//import java.io.IOException; -// -//import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; -//import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; -// -////WebSocket处理器 -//@Component("machineWebSocketHandler") -//public class MachineWebSocketHandler extends TextWebSocketHandler { -// -// @Override -// public void afterConnectionEstablished(@NotNull WebSocketSession webSocketSession) { -// Long machineId = (Long) webSocketSession.getAttributes().get("machineId"); -// //保存webSocketSession信息 -// WebSocketSessionManager.addWebSocketSession(webSocketSession.getId(), webSocketSession); -// WebSocketSessionManager.addWebSocketConnection(webSocketSession.getId(), WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId)); -// try { -// webSocketSession.sendMessage(new TextMessage("欢迎连接到 WebSocket 服务器!")); -// } catch (IOException e) { -// throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); -// } -// } -// -// // 处理文本消息 -// @Override -// protected void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) { -// String payload = message.getPayload(); -// String sessionId = webSocketSession.getId(); -// // 从管理器获取连接 -// WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId); -// -// if (webSocketConnection != null && ConnectionStatus.CONNECTING.equals(webSocketConnection.getConnectionStatus())) { -// // 转发消息到远程机器 -// webSocketConnection.executeCommand(webSocketSession, payload); -// } else if (webSocketConnection != null) { -// webSocketConnection.sendErrorMessage(webSocketSession, "连接已断开,无法发送消息"); -// } else { -// throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); -// } -// } -// -// @Override -// public void afterConnectionClosed(WebSocketSession webSocketSession, @NotNull CloseStatus status) { -// String sessionId = webSocketSession.getId(); -// // 获取并关闭相关的 WebSocketConnection -// WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId); -// if (webSocketConnection != null) { -// webSocketConnection.disconnect(); -// } -// // 从管理器中移除会话和连接 -// WebSocketSessionManager.removeWebSocketSession(sessionId); -// WebSocketSessionManager.removeWebSocketConnection(sessionId); -// Long machineInfoId = (Long) webSocketSession.getAttributes().get("machineId"); -// WebSocketSessionManager.removeWebSocketConnectionByMachineId(machineInfoId); -// } -//} +package cd.casic.module.machine.handler; + +import cd.casic.module.machine.component.WebSocketConnection; +import cd.casic.module.machine.component.WebSocketSessionManager; +import cd.casic.module.machine.enums.ConnectionStatus; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; + +import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; +import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; + +//WebSocket处理器 +@Component("machineWebSocketHandler") +public class MachineWebSocketHandler extends TextWebSocketHandler { + + @Override + public void afterConnectionEstablished(@NotNull WebSocketSession webSocketSession) { + Long machineId = (Long) webSocketSession.getAttributes().get("machineId"); + //保存webSocketSession信息 + WebSocketSessionManager.addWebSocketConnection(webSocketSession.getId(), WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId)); + try { + webSocketSession.sendMessage(new TextMessage("欢迎连接到 WebSocket 服务器!")); + } catch (IOException e) { + throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); + } + } + + // 处理文本消息 + @Override + protected void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) { + String payload = message.getPayload(); + String sessionId = webSocketSession.getId(); + // 从管理器获取连接 + WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId); + if (webSocketConnection != null && ConnectionStatus.CONNECTING.equals(webSocketConnection.getConnectionStatus())) { + // 转发消息到远程机器 + webSocketConnection.executeCommand(webSocketSession, payload); + } else if (webSocketConnection != null) { + webSocketConnection.sendErrorMessage(webSocketSession, "连接已断开,无法发送消息"); + } else { + throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession webSocketSession, @NotNull CloseStatus status) { + String sessionId = webSocketSession.getId(); + // 获取并关闭相关的 WebSocketConnection + WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId); + if (webSocketConnection != null) { + webSocketConnection.disconnect(); + } + // 从管理器中移除会话和连接 + WebSocketSessionManager.removeWebSocketConnection(sessionId); + Long machineInfoId = (Long) webSocketSession.getAttributes().get("machineId"); + WebSocketSessionManager.removeWebSocketConnectionByMachineId(machineInfoId); + } +} 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 ea5fb26a..6aa2eb92 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 @@ -2,9 +2,12 @@ package cd.casic.module.machine.service; import cd.casic.framework.commons.pojo.PageResult; import cd.casic.module.machine.controller.vo.MachineInfoVO; +import cd.casic.module.machine.controller.vo.SecretKeyVO; import cd.casic.module.machine.dal.dataobject.MachineInfoDO; import cd.casic.module.machine.enums.ConnectionStatus; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; +import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; @@ -18,18 +21,35 @@ public interface MachineInfoService { /** * 查看机器列表 - * @return 分院 + * @return 机器列表 */ PageResult listMachineInfo(@Valid MachineInfoVO MachineInfoVO); + /** + * 更新机器信息 + */ void updateMachineInfo(@Valid MachineInfoVO machineInfoVO); + /** + * 变更机器状态 + */ Integer updateStatus(@Valid MachineInfoVO machineInfoVO); - void bindingSecretKey(List machineInfoIds, Long secretKeyId); + /** + * 绑定密钥 + */ + void bindingSecretKey(SecretKeyVO secretKeyVO); + /** + * 批量删除机器 + * @param machineInfoIds 机器id列表 + */ void deleteMachineInfoList(String machineInfoIds); + /** + * 删除机器信息 + * @param machineInfoId 机器id + */ void deleteMachineInfo(Long machineInfoId); /** @@ -40,55 +60,51 @@ public interface MachineInfoService { ListselectBindMachineBySecretKey(Long secretKeyId); -// /** -// * 测试机器连接 -// * -// * @param id 机器id -// * @return 连接是否成功 -// */ -// boolean testConnection(Long id); -// -// /** -// * 连接远程机器 -// * -// * @param id 机器id -// * @return 连接后端文件树 -// */ -// Map connect(Long id); -// -// /** -// * 获取机器连接状态 -// * -// * @return 连接状态 -// */ -// ConnectionStatus getConnectionStatus(Long id); -// -// /** -// * 获取所有连接状态 -// * -// * @return 机器名称到连接状态的映射 -// */ -// Map getAllConnectionStatus(); + /** + * 测试机器连接 + * @param id 机器id + * @return 连接是否成功 + */ + boolean testConnection(Long id); -// /** -// * 上传文件到远程机器 -// * -// * @param sessionId 会话ID -// * @param localFilePath 本地文件路径 -// * @param remoteFilePath 远程文件路径 -// * @return 操作结果 -// */ -// boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath); -// -// /** -// * 从远程机器下载文件 -// * -// * @param sessionId 会话ID -// * @param remoteFilePath 远程文件路径 -// * @param localFilePath 本地文件路径 -// * @return 操作结果 -// */ -// boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath); + /** + * 连接远程机器 + * + * @param id 机器id + * @return 连接后端文件树 + */ + Map connect(Long id); + + /** + * 获取机器连接状态 + * + * @return 连接状态 + */ + ConnectionStatus getConnectionStatus(Long id); + + /** + * 获取所有连接状态 + * + * @return 机器名称到连接状态的映射 + */ + Map getAllConnectionStatus(); + + /** + * 上传文件到远程机器 + * + * @param file 上传文件 + * @param remoteFilePath 远程文件路径 + * @return 操作结果 + */ + boolean uploadFile(MultipartFile file, String remoteFilePath); + + /** + * 从远程机器下载文件 + * + * @param remoteFilePath 远程文件路径 + * @return 操作结果 + */ + boolean downloadFile(String remoteFilePath, HttpServletResponse httpServletResponse); /** * 校验机器是否存在 @@ -97,12 +113,18 @@ public interface MachineInfoService { */ MachineInfoDO validateMachineInfoExists(Long id); -// /** -// * 根据路径获得远程文件树 -// * -// * @param machineId 机器id -// * @param path 文件夹路径 -// * @return 远程文件树 -// */ -// Map fileTreeNode(Long machineId, String path); + /** + * 根据路径获得远程文件树 + * + * @param machineId 机器id + * @param path 文件夹路径 + * @return 远程文件树 + */ + Map fileTreeNode(Long machineId, String path); + + /** + * 删除远程文件路径 + * @param remoteFilePath 远程文件路径 + */ + boolean deleteRemoteFile(String remoteFilePath); } diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineProxyService.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineProxyService.java index 0c699356..c92596ac 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineProxyService.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/MachineProxyService.java @@ -26,7 +26,6 @@ public interface MachineProxyService { /** * 删除代理 * - * @param */ void delete(Long id); @@ -45,5 +44,8 @@ public interface MachineProxyService { */ void deleteProxyList(String proxyIds); + /** + * 获取代理列表(分页) + */ PageResult getProxyPage(@Valid MachineProxyVO machineProxyVO); } 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 1436b582..19326057 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 @@ -5,30 +5,44 @@ import cd.casic.module.machine.dal.dataobject.MachineInfoDO; import cd.casic.module.machine.dal.dataobject.SecretKeyDO; import cd.casic.module.machine.controller.vo.SecretKeyVO; import jakarta.validation.Valid; -import org.springframework.core.io.InputStreamResource; -import org.springframework.http.ResponseEntity; import java.util.List; public interface SecretKeyService { - Long createSecretKey(@Valid SecretKeyVO secretKeyVO) throws Exception; - - void bindingMachine(@Valid SecretKeyVO secretKeyVO); + /** + * 新增密钥 + */ + Long createSecretKey(@Valid SecretKeyVO secretKeyVO); + /** + * 更新密钥 + */ void updateSecretKey(@Valid SecretKeyVO secretKeyVO); - PageResult getSecretKeypage(@Valid SecretKeyVO secretKeyVO); + /** + * 获取密钥列表(分页) + */ + PageResult getSecretKeyPage(@Valid SecretKeyVO secretKeyVO); + /** + * 批量删除密钥 + * @param ids 密钥id集合 + */ void deleteSecretKeyList(List ids); + /** + * 根据id获取密钥对象 + */ SecretKeyVO getSecretKey(Long id); - void deleteSecretKey(Long id); - - void unbindMachine(@Valid SecretKeyVO secretKeyVO); - + /** + * 获取绑定的密钥的机器列表 + */ List getBindMachine(Long secretKeyId); -// String getKeyContent(Long secretKeyId); + /** + * 获取公钥内容 + */ + String getPublicKeyContent(Long secretKeyId); } 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 99799623..3099f9fa 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 @@ -2,7 +2,8 @@ package cd.casic.module.machine.service.impl; import cd.casic.framework.commons.pojo.PageResult; import cd.casic.module.machine.component.FileTreeComponent; -//import cd.casic.module.machine.component.WebSocketSessionManager; +import cd.casic.module.machine.component.WebSocketConnection; +import cd.casic.module.machine.component.WebSocketSessionManager; import cd.casic.module.machine.controller.vo.SecretKeyVO; import cd.casic.module.machine.enums.AuthenticationType; import cd.casic.module.machine.enums.MachineInfoType; @@ -11,17 +12,22 @@ 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.component.WebSocketConnection; import cd.casic.module.machine.service.MachineInfoService; import cd.casic.module.machine.service.SecretKeyService; import com.google.common.annotations.VisibleForTesting; import com.jcraft.jsch.Session; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; 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 org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; @@ -44,6 +50,9 @@ public class MachineInfoServiceImpl implements MachineInfoService { @Resource private FileTreeComponent fileTreeComponent; + //todo,部署后更改 + private static final Path TEMP_DIR = Paths.get("D:\\桌面\\work\\ops-pro111\\temp"); + @Override public Long createMachine(MachineInfoVO machineInfoVO) { validateMachineEnvAdd(machineInfoVO); @@ -85,8 +94,8 @@ public class MachineInfoServiceImpl implements MachineInfoService { } @Override - public void bindingSecretKey(List machineInfoIds, Long secretKeyId) { - machineInfoMapper.bindingSecretKey(machineInfoIds, secretKeyId); + public void bindingSecretKey(SecretKeyVO secretKeyVO) { + machineInfoMapper.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId(),secretKeyVO.getEnableBind()); } @Override @@ -109,78 +118,73 @@ public class MachineInfoServiceImpl implements MachineInfoService { @Override public List selectBindMachineBySecretKey(Long secretKeyId) { - return machineInfoMapper.selectBindMachineBySecretKey(secretKeyId); + return machineInfoMapper.selectBindMachineBySecretKey(secretKeyId); } -// @Override -// public boolean testConnection(Long id) { -// //先查询机器是否存在,在判断机器可用性 -// MachineInfoDO machineInfoDO = validateMachineInfoExists(id); -// validateMachineUnEnable(machineInfoDO); -// log.info("测试机器连接: {}", machineInfoDO.getHostIp()); -// WebSocketConnection webSocketConnection = createWebSocketConnection(machineInfoDO); -// webSocketConnection.initConnection(machineInfoDO); -// return true; -// } -// -// @Override -// public Map connect(Long id) { -// //todo使用代理机器的情况还未完成 -// WebSocketConnection webSocketConnection = new WebSocketConnection(this.secretKeyService); -// MachineInfoDO machineInfoDO = validateMachineInfoExists(id); -// //初始化连接 -// webSocketConnection.initConnection(machineInfoDO); -// WebSocketSessionManager.addWebSocketConnectionByMachineId(id, webSocketConnection); -// return fileTreeComponent.getRemoteFileTree(webSocketConnection.getSshSession(), "/"); -// } -// -// @Override -// public ConnectionStatus getConnectionStatus(Long id) { -// validateMachineInfoExists(id); -// WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnectionByMachineId(id); -// return webSocketConnection == null ? ConnectionStatus.DISCONNECTED : webSocketConnection.getConnectionStatus(); -// } -// -// @Override -// public Map getAllConnectionStatus() { -// return WebSocketSessionManager.getAllWebSocketConnections().entrySet().stream() -// .collect(Collectors.toMap( -// Map.Entry::getKey, -// entry -> entry.getValue().getConnectionStatus() -// )); -// } + @Override + public boolean testConnection(Long id) { + //先查询机器是否存在,在判断机器可用性 + MachineInfoDO machineInfoDO = validateMachineInfoExists(id); + validateMachineUnEnable(machineInfoDO); + log.info("测试机器连接: {}", machineInfoDO.getHostIp()); + WebSocketConnection webSocketConnection = createWebSocketConnection(machineInfoDO); + webSocketConnection.initConnection(machineInfoDO); + return true; + } -// @Override -// public boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath) { -// log.info("上传文件: {} -> {}, 会话ID: {}", localFilePath, remoteFilePath, sessionId); -// WebSocketConnection webSocketConnection = sessionConnectionMap.get(sessionId); -// if (webSocketConnection == null||webSocketConnection.getConnectionStatus() != ConnectionStatus.CONNECTED) { -// throw exception(SESSION_NOT_CONNECT); -// } -// try { -// return webSocketConnection.uploadFile(localFilePath, remoteFilePath); -// } catch (Exception e) { -// log.error("文件上传失败: {}", e.getMessage(), e); -// throw exception(FILE_UPLOAD_ERROR); -// } -// } + @Override + public Map connect(Long id) { + //todo使用代理机器的情况还未完成 + WebSocketConnection webSocketConnection = new WebSocketConnection(this.secretKeyService); + MachineInfoDO machineInfoDO = validateMachineInfoExists(id); + //初始化连接 + webSocketConnection.initConnection(machineInfoDO); + WebSocketSessionManager.addWebSocketConnectionByMachineId(id, webSocketConnection); + return fileTreeComponent.getRemoteFileTree(webSocketConnection.getSshSession(), "/"); + } -// @Override -// public boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath) { -// log.info("下载文件: {} -> {}, 会话ID: {}", remoteFilePath, localFilePath, sessionId); -// -// WebSocketConnection webSocketConnection = sessionConnectionMap.get(sessionId); -// if (webSocketConnection == null||webSocketConnection.getConnectionStatus() != ConnectionStatus.CONNECTED) { -// throw new RuntimeException("会话不存在: " + sessionId); -// } -// try { -// return webSocketConnection.downloadFile(remoteFilePath, localFilePath); -// } catch (Exception e) { -// log.error("文件下载失败: {}", e.getMessage(), e); -// throw exception(FILE_DOWNLOAD_ERROR); -// } -// } + @Override + public ConnectionStatus getConnectionStatus(Long id) { + validateMachineInfoExists(id); + WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnectionByMachineId(id); + return webSocketConnection == null ? ConnectionStatus.DISCONNECTED : webSocketConnection.getConnectionStatus(); + } + + @Override + public Map getAllConnectionStatus() { + return WebSocketSessionManager.getAllWebSocketConnections().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().getConnectionStatus() + )); + } + + @Override + public boolean uploadFile(MultipartFile file, String remoteFilePath) { + Path tempFilePath; + // 保存上传的文件到临时位置 + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || originalFilename.isEmpty()) { + throw exception(INVALID_FILE_NAME); + } + // 安全过滤文件名,防止路径遍历攻击 + String safeFilename = sanitizeFilename(originalFilename); + try { + tempFilePath = Files.createTempFile(TEMP_DIR, "upload-", safeFilename); + file.transferTo(tempFilePath.toFile()); + } catch (IOException e) { + throw exception(CREATE_TEMP_FILE_ERROR); + } + fileTreeComponent.uploadToRemoteServer(tempFilePath, safeFilename, remoteFilePath); + return true; + } + + @Override + public boolean downloadFile(String remoteFilePath, HttpServletResponse httpServletResponse) { + fileTreeComponent.downloadFile(remoteFilePath, httpServletResponse); + return true; + } @VisibleForTesting void validateMachineEnvAdd(MachineInfoVO machineInfoVO) { @@ -246,12 +250,18 @@ public class MachineInfoServiceImpl implements MachineInfoService { return machineInfoDO; } -// @Override -// public Map fileTreeNode(Long machineId, String path) { -// validateMachineInfoExists(machineId); -// Session sshSession = WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId).getSshSession(); -// return fileTreeComponent.getRemoteFileTree(sshSession, path); -// } + @Override + public Map fileTreeNode(Long machineId, String path) { + validateMachineInfoExists(machineId); + Session sshSession = WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId).getSshSession(); + return fileTreeComponent.getRemoteFileTree(sshSession, path); + } + + @Override + public boolean deleteRemoteFile(String remoteFilePath) { + fileTreeComponent.deleteRemoteFile(remoteFilePath); + return true; + } @VisibleForTesting void validateMachineEnable(MachineInfoDO machineInfoDO) { @@ -268,12 +278,25 @@ public class MachineInfoServiceImpl implements MachineInfoService { } } -// @VisibleForTesting -// WebSocketConnection createWebSocketConnection(MachineInfoDO machineInfoDO) { -// if (WebSocketSessionManager.containsMachineId(machineInfoDO.getId())) { -// return WebSocketSessionManager.getWebSocketConnectionByMachineId((machineInfoDO.getId())); -// } else { -// return new WebSocketConnection(this.secretKeyService); -// } -// } + @VisibleForTesting + WebSocketConnection createWebSocketConnection(MachineInfoDO machineInfoDO) { + if (WebSocketSessionManager.containsMachineId(machineInfoDO.getId())) { + return WebSocketSessionManager.getWebSocketConnectionByMachineId((machineInfoDO.getId())); + } else { + return new WebSocketConnection(this.secretKeyService); + } + } + + @VisibleForTesting + // 安全过滤文件名,防止路径遍历攻击 + private String sanitizeFilename(String filename) { + // 移除路径相关字符,只保留文件名和扩展名 + filename = filename.replaceAll("[\\\\/:*?\"<>|]", "_"); + // 限制最大长度 + if (filename.length() > 255) { + filename = filename.substring(0, 255); + } + + return filename; + } } 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 eec46f33..c20e31ba 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 @@ -7,9 +7,6 @@ import cd.casic.module.machine.dal.dataobject.MachineInfoDO; 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; @@ -33,36 +30,28 @@ public class SecretKeyServiceImpl implements SecretKeyService { @Resource private SecretKeyMapper secretKeyMapper; - @Override - public void deleteSecretKey(Long id) { - validateSecretKeyExists(id); - secretKeyMapper.deleteById(id); - } - @Override public SecretKeyVO getSecretKey(Long id) { SecretKeyDO secretKeyDO = validateSecretKeyExists(id); return BeanUtils.toBean(secretKeyDO, SecretKeyVO.class); } - @Override - public void unbindMachine(SecretKeyVO secretKeyVO) { - machineInfoService.bindingSecretKey(secretKeyVO.getMachineInfoIds(),null); - } - @Override public List getBindMachine(Long secretKeyId) { return machineInfoService.selectBindMachineBySecretKey(secretKeyId); } + @Override + public String getPublicKeyContent(Long secretKeyId) { + return secretKeyMapper.selectById(secretKeyId).getPublicKey(); + } + @Override public Long createSecretKey(SecretKeyVO secretKeyVO) { validateSecretKeyAdd(secretKeyVO); SecretKeyDO secretKeyDO = BeanUtils.toBean(secretKeyVO, SecretKeyDO.class); secretKeyMapper.insert(secretKeyDO); return secretKeyDO.getId(); - - } @Override @@ -72,25 +61,21 @@ public class SecretKeyServiceImpl implements SecretKeyService { secretKeyMapper.updateById(secretKeyDO); } - @Override - public void bindingMachine(SecretKeyVO secretKeyVO) { - validateSecretKeyExists(secretKeyVO.getId()); - machineInfoService.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId()); - } - @Override @Transactional public void deleteSecretKeyList(List ids) { - machineInfoService.bindingSecretKey(ids,null); + SecretKeyVO secretKeyVO = new SecretKeyVO() + .setMachineInfoIds(ids) + .setEnableBind(false); + machineInfoService.bindingSecretKey(secretKeyVO); secretKeyMapper.deleteBatchIds(ids); } @Override - public PageResult getSecretKeypage(SecretKeyVO secretKeyVO) { + public PageResult getSecretKeyPage(SecretKeyVO secretKeyVO) { return secretKeyMapper.selectPage(secretKeyVO); } - @VisibleForTesting void validateSecretKeyAdd(SecretKeyVO secretKeyVO) { if (secretKeyVO == null) { @@ -98,8 +83,6 @@ public class SecretKeyServiceImpl implements SecretKeyService { } } - - @VisibleForTesting SecretKeyDO validateSecretKeyExists(Long id) { if (id == null) { @@ -111,5 +94,4 @@ public class SecretKeyServiceImpl implements SecretKeyService { } return secretKeyDO; } - }