From d30f11a47a2bfa6cf83d884b432e343342188011 Mon Sep 17 00:00:00 2001 From: zyj <2660555181@qq.com> Date: Wed, 9 Jul 2025 11:33:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E9=99=A4=E6=9C=BA=E5=99=A8=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86=E9=83=A8?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/module-ci-machine/pom.xml | 13 - .../WebSocketHandshakeInterceptor.java | 85 ----- .../machine/component/FileTreeComponent.java | 340 ------------------ .../component/WebSocketConnection.java | 228 ------------ .../component/WebSocketSessionManager.java | 70 ---- .../configuration/AliYunOssConfig.java | 33 -- .../configuration/WebSocketConfig.java | 40 --- .../contants/MachineErrorCodeConstants.java | 30 +- .../controller/MachineInfoController.java | 56 --- .../module/machine/dal/model/FileNode.java | 56 --- .../machine/dal/mysql/SecretKeyMapper.java | 10 - .../machine/enums/ConnectionStatus.java | 33 -- .../module/machine/enums/SSHChanelType.java | 35 -- .../handler/MachineWebSocketHandler.java | 64 ---- .../machine/service/MachineInfoService.java | 75 +--- .../service/impl/MachineInfoServiceImpl.java | 128 +------ .../module/machine/utils/AliYunOssClient.java | 11 - .../module/machine/utils/CryptogramUtil.java | 32 +- 18 files changed, 25 insertions(+), 1314 deletions(-) delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/Interceptor/WebSocketHandshakeInterceptor.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/FileTreeComponent.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketConnection.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketSessionManager.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/AliYunOssConfig.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/WebSocketConfig.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/model/FileNode.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/ConnectionStatus.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/SSHChanelType.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java delete mode 100644 modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/AliYunOssClient.java diff --git a/modules/module-ci-machine/pom.xml b/modules/module-ci-machine/pom.xml index 6f97ecf3..2e8e1b2c 100644 --- a/modules/module-ci-machine/pom.xml +++ b/modules/module-ci-machine/pom.xml @@ -34,24 +34,11 @@ - - com.aliyun.oss - aliyun-sdk-oss - 3.15.1 - - - org.springframework.boot spring-boot-starter-websocket - - - com.jcraft - jsch - 0.1.55 - com.antherd sm-crypto diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/Interceptor/WebSocketHandshakeInterceptor.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/Interceptor/WebSocketHandshakeInterceptor.java deleted file mode 100644 index a0a6f890..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/Interceptor/WebSocketHandshakeInterceptor.java +++ /dev/null @@ -1,85 +0,0 @@ -package cd.casic.module.machine.Interceptor; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.HandshakeInterceptor; - -import java.net.URI; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; -import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; - -@Component -@Slf4j -public class WebSocketHandshakeInterceptor implements HandshakeInterceptor { - @Override - public boolean beforeHandshake( - @NotNull ServerHttpRequest request, - @NotNull ServerHttpResponse response, - @NotNull WebSocketHandler wsHandler, - @NotNull Map attributes - ) { - log.info("WebSocket握手请求: {}", request.getURI()); - - // 从URL参数中获取id - String id = extractIdFromUrl(request.getURI()); - - System.out.println("-----------------------------------------"); - if (id != null) { - attributes.put("machineId", id); // 将id存入attributes - log.info("握手验证成功 - ID: {}", id); - return true; - } else { - response.setStatusCode(HttpStatus.BAD_REQUEST); - log.warn("握手验证失败 - 缺少 id URL 参数"); - return false; - } - } - - @Override - public void afterHandshake( - @NotNull ServerHttpRequest request, - @NotNull ServerHttpResponse response, - @NotNull WebSocketHandler wsHandler, - Exception exception - ) { - if (exception == null) { - log.info("WebSocket握手完成 - URI: {}", - request.getURI()); - } else { - log.error("WebSocket握手异常 - URI: {}, 异常信息: {}", - request.getURI(), - exception.getMessage(), - exception); - } - } - - // 从URI中提取id参数的辅助方法 - private String extractIdFromUrl(URI uri) { - try { - String query = uri.getQuery(); - if (query != null) { - String[] params = query.split("&"); - for (String param : params) { - String[] keyValue = param.split("="); - if (keyValue.length == 2 && "id".equalsIgnoreCase(keyValue[0])) { // 修改为匹配id - return URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8); - } - } - } - } catch (Exception e) { - log.error("解析URL参数失败", e); - throw exception(FAILED_TO_PARSE_URL_PARAMETERS); - } - return null; - } - -} 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 deleted file mode 100644 index 5e967815..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/FileTreeComponent.java +++ /dev/null @@ -1,340 +0,0 @@ -package cd.casic.module.machine.component; - -import cd.casic.module.machine.dal.model.FileNode; -import com.jcraft.jsch.Channel; -import com.jcraft.jsch.ChannelSftp; -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; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; -import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; - -@Slf4j -@Component -public class FileTreeComponent { - - private ChannelSftp sftp; - //todo缓存过期还未设置 - private final Map directoryCache = new ConcurrentHashMap<>(); - private static final long CACHE_EXPIRE_TIME = 60 * 1000; // 缓存过期时间:1分钟 - private final Map cacheTimeStamp = new ConcurrentHashMap<>(); - - // 将文件树转换为JSON友好的Map结构(仅一级目录) - public static Map convertToMap(FileNode node) { - Map map = new HashMap<>(); - map.put("name", node.getName()); - map.put("isDirectory", node.isDirectory()); - map.put("size", node.getSize()); - map.put("permissions", node.getPermissions()); - map.put("modifiedTime", node.getModifiedTime()); - // 仅添加直接子项,不递归 - if (node.isDirectory() && !node.getChildren().isEmpty()) { - List> children = node.getChildren().stream() - .map(FileTreeComponent::convertToMap) - .collect(Collectors.toList()); - map.put("children", children); - } - return map; - } - - // 获取指定路径的直接子项(不递归) - public List listDirectChildren(String path) { - if (sftp == null || !isSftpConnected()) { - log.error("SFTP连接无效,无法列出文件"); - throw exception(CONNECTION_LOST); - } - - List entries = Collections.synchronizedList(new ArrayList<>()); - //定义ChannelSftp下LsEntrySelector接口中select方法 - ChannelSftp.LsEntrySelector selector = entry -> { - entries.add(entry); - return ChannelSftp.LsEntrySelector.CONTINUE; - }; - - try { - sftp.ls(path, selector); - } catch (SftpException e) { - log.error("读取远程文件目录结构失败, 信息: {}]", e.getMessage(), e); - throw exception(READ_REMOTE_DIRECTORY_FAIL); - } - return new ArrayList<>(entries); - } - - // 创建文件节点(支持使用完整路径或文件名) - private static FileNode createFileNode(String name, SftpATTRS attrs, boolean useFullPath) { - String displayName = useFullPath ? name : name.substring(name.lastIndexOf('/') + 1); - return new FileNode( - displayName, - attrs.isDir(), - attrs.getSize(), - attrs.getPermissionsString(), - attrs.getMTime() * 1000L // 转换为毫秒 - ); - } - - // 获取远程文件树的方法(仅展示下一级) - public Map getRemoteFileTree(Session session, String path) { - path = normalizePath(path); - Channel channel = null; - try { - // 缓存有效性检查 - String cacheKey = "ls:" + path; - long currentTime = System.currentTimeMillis(); - - // 检查缓存是否存在且未过期 - if (directoryCache.containsKey(cacheKey) && - currentTime - cacheTimeStamp.getOrDefault(cacheKey, 0L) < CACHE_EXPIRE_TIME) { - log.debug("从缓存获取目录内容: {}", path); - // 构建缓存中的根节点 - FileNode root = directoryCache.get(cacheKey); - return sortFileInfo(convertToMap(root)); - } - - // 打开SFTP通道 - channel = session.openChannel("sftp"); - channel.connect(); - sftp = (ChannelSftp) channel; - - // 先通过stat获取当前路径信息 - SftpATTRS rootAttrs = sftp.stat(path); - FileNode root = createFileNode(path, rootAttrs, true); - - // 仅获取直接子项,不递归 - List entries = listDirectChildren(path); - //循环添加子节点 - for (ChannelSftp.LsEntry entry : entries) { - String fileName = entry.getFilename(); - if (fileName.equals(".") || fileName.equals("..")) continue; - SftpATTRS attrs = entry.getAttrs(); - FileNode childNode = createFileNode(fileName, attrs, false); - root.addChild(childNode); - } - // 更新缓 - directoryCache.put(cacheKey, root); - cacheTimeStamp.put(cacheKey, currentTime); - return sortFileInfo(convertToMap(root)); - } catch (JSchException e) { - log.error("SFTP通道创建或连接失败: {}", e.getMessage(), e); - if (e.getMessage().contains("open")) { - throw exception(CREATE_CHANEL_ERROR); - } else { - throw exception(CHANEL_CONNECT_FAIL); - } - } catch (SftpException e) { - if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) { - // 仅捕获路径不存在的错误 - log.error("路径不存在: {}", path); - throw exception(PATH_NOT_EXISTS); // 自定义异常 - } else if (e.id == ChannelSftp.SSH_FX_PERMISSION_DENIED) { - // 处理权限问题(非路径不存在) - log.error("无路径访问权限: {}", path); - throw exception(NO_PATH_PERMISSION); - } else { - log.error("获取目录内容失败: {}", e.getMessage(), e); - throw exception(READ_REMOTE_DIRECTORY_FAIL); - } - } finally { - // 确保资源释放 - if (sftp != null) { - try { - sftp.disconnect(); - } catch (Exception ex) { - log.warn("关闭SFTP连接失败", ex); - } - } - if (channel != null && channel.isConnected()) { - channel.disconnect(); - } - } - } - - // 检查SFTP连接状态 - private boolean isSftpConnected() { - if (sftp == null) return false; - try { - sftp.pwd(); - return true; - } catch (Exception e) { - log.warn("SFTP连接状态检查失败", e); - return false; - } - } - - // 规范化路径:确保末尾有斜杠(根路径除外) - private String normalizePath(String path) { - // 移除多余的斜杠(多个连续的斜杠会被替换为一个) - path = path.replaceAll("/+", "/"); - - // 如果路径不为根路径且末尾没有斜杠,则添加斜杠 - if (!"/".equals(path) && !path.endsWith("/")) { - path += "/"; - } - - return path; - } - - /** - * 对文件和目录信息进行排序(仅处理直接子级) - * - * @param fileInfoMap 文件/目录信息映射 - * @return 排序后的映射 - */ - public Map sortFileInfo(Map fileInfoMap) { - // 检查是否包含子节点 - if (fileInfoMap.containsKey("children")) { - List> children = (List>) fileInfoMap.get("children"); - // 对子节点列表进行排序 - if (children != null && !children.isEmpty()) { - children.sort((a, b) -> { - // 获取isDirectory属性并比较 - boolean isADirectory = (boolean) a.get("isDirectory"); - boolean isBDirectory = (boolean) b.get("isDirectory"); - // 目录排在文件前面 - return Boolean.compare(isBDirectory, isADirectory); - }); - // 更新排序后的子节点列表 - fileInfoMap.put("children", children); - } - } - 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 deleted file mode 100644 index 6f9fcd91..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketConnection.java +++ /dev/null @@ -1,228 +0,0 @@ -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 cd.casic.module.machine.utils.CryptogramUtil; -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 javax.script.ScriptException; -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; - try { - pubKeyContent = CryptogramUtil.doDecrypt(secretKeyService.getPublicKeyContent(machineInfo.getSecretKeyId())); - } catch (ScriptException e) { - throw exception(ENCRYPT_OR_DECRYPT_FAIL); - } - // 验证秘钥格式 - 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 deleted file mode 100644 index 24e17f0b..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/component/WebSocketSessionManager.java +++ /dev/null @@ -1,70 +0,0 @@ -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/AliYunOssConfig.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/AliYunOssConfig.java deleted file mode 100644 index fc66eda6..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/AliYunOssConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -package cd.casic.module.machine.configuration; - -import cd.casic.module.infra.framework.file.core.client.s3.S3FileClientConfig; -import cd.casic.module.machine.utils.AliYunOssClient; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class AliYunOssConfig extends S3FileClientConfig{ - @Value("${aliyun.oss.endpoint}") - private String endpoint; - @Value("${aliyun.oss.accessKeyId}") - private String accessKey; - @Value("${aliyun.oss.accessKeySecret}") - private String accessSecret; - @Value("${aliyun.oss.bucketName}") - private String bucket; - // 定义 S3 客户端 Bean - @Bean - public AliYunOssClient aliYunClient() { - // 创建配置对象 - S3FileClientConfig config = new AliYunOssConfig(); - config.setEndpoint(endpoint); - config.setAccessKey(accessKey); - config.setAccessSecret(accessSecret); - config.setBucket(bucket); - AliYunOssClient aliYunOssClient = new AliYunOssClient(1L, config); - // 创建并返回客户端实例 - aliYunOssClient.init(); - return aliYunOssClient; - } -} 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 deleted file mode 100644 index c44d5d9e..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/configuration/WebSocketConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -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 bc3b9614..a526d9e5 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 @@ -17,7 +17,6 @@ public interface MachineErrorCodeConstants { ErrorCode MACHINE_INFO_AUTHENTICATION_TYPE_NULL = new ErrorCode(1_003_000_007, "机器认证类型为空"); ErrorCode MACHINE_INFO_AUTHENTICATION_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_008, "机器认证类型不存在"); ErrorCode MACHINE_ENABLE = new ErrorCode(1_003_000_009, "机器启用中"); - ErrorCode MACHINE_UN_ENABLE = new ErrorCode(1_003_000_010, "机器不可用"); // ========== 机器环境变量模块 1-003-002-000 ========== ErrorCode MACHINE_ENV_NULL = new ErrorCode(1_003_002_000, "机器环境变量为空"); @@ -34,32 +33,5 @@ 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 INVALID_kEY_FORMAT = new ErrorCode(1_003_004_002, "无效的密钥格式"); - ErrorCode READ_SECRET_CONTENT_ERROR = new ErrorCode(1_003_004_003, "读取密钥加载失败"); - ErrorCode ENCRYPT_OR_DECRYPT_FAIL = new ErrorCode(1_003_004_004, "加密/解密失败"); - - //========== 会话连接模块 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 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, "非目录节点不能添加子节点"); - ErrorCode READ_REMOTE_DIRECTORY_FAIL = new ErrorCode(1_003_007_002, "读取远程文件目录结构失败"); - 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, "删除文件时出错"); + ErrorCode ENCRYPT_OR_DECRYPT_FAIL = new ErrorCode(1_003_004_002, "加密/解密失败"); } 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 a6cfd928..e0abd893 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 @@ -5,20 +5,15 @@ 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; 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.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; import static cd.casic.framework.commons.pojo.CommonResult.success; @@ -85,55 +80,4 @@ public class MachineInfoController { public void bindingSecretKey(@RequestBody SecretKeyVO secretKeyVO) { machineInfoService.bindingSecretKey(secretKeyVO); } - - @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/dal/model/FileNode.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/model/FileNode.java deleted file mode 100644 index 9741fa0d..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/dal/model/FileNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package cd.casic.module.machine.dal.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; -import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; - -import java.util.ArrayList; -import java.util.List; - -//文件节点类 -@Data -/* - * 远程文件系统的节点信息,用于构建文件树结构 - */ -@Schema(description = "远程文件系统节点") -public class FileNode { - @Schema(description = "文件名或目录名") - private String name; - - @Schema(description = "是否为目录") - private boolean isDirectory; - - @Schema(description = "文件大小(字节)") - private long size; - - @Schema(description = "文件权限字符串(如 rwxr-xr-x)") - private String permissions; - - @Schema(description = "最后修改时间(时间戳,毫秒)") - private long modifiedTime; - - @Schema(description = "子节点列表(仅目录有此属性)") - private List children; - - public FileNode(String name, boolean isDirectory, long size, String permissions, long modifiedTime) { - this.name = name; - this.isDirectory = isDirectory; - this.size = size; - this.permissions = permissions; - this.modifiedTime = modifiedTime; - - if (isDirectory) { - this.children = new ArrayList<>(); - } - - } - - public void addChild(FileNode child) { - if (this.children == null) { - throw exception(NOT_DIRECTORY_NODE); - } - this.children.add(child); - } -} 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 index f159c478..bb530946 100644 --- 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 @@ -4,13 +4,9 @@ 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 { //查询列表 @@ -19,10 +15,4 @@ public interface SecretKeyMapper extends BaseMapperX { .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/enums/ConnectionStatus.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/ConnectionStatus.java deleted file mode 100644 index a4c81e5f..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/ConnectionStatus.java +++ /dev/null @@ -1,33 +0,0 @@ -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 ConnectionStatus implements IntArrayValuable { - DISCONNECTED(1, "断开连接"), - CONNECTING(2, "正在连接"), - CONNECTED(3, "已连接"), - AUTH_FAILED(4, "认证失败"), - CONNECTION_TIMEOUT(5, "连接超时"), - CONNECTION_ERROR(6, "连接错误"), - CLOSED(7, "已关闭"); - - private final int code; - - private final String message; - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ConnectionStatus::getCode).toArray(); - - @Override - public int[] array() { - return ARRAYS; - } -} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/SSHChanelType.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/SSHChanelType.java deleted file mode 100644 index c915797e..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/enums/SSHChanelType.java +++ /dev/null @@ -1,35 +0,0 @@ -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 -//ssh远程连接管道类型 -public enum SSHChanelType implements IntArrayValuable { - EXEC(1, "单条命令返回结果"), - SHELL(2, "多行命令持续交互"), - SUBSYSTEM(3, "文件传输或其它特点服务"), - DIRECT_TCP_IP(4, "TCP 端口转发"), - X11(5, "X Window 系统转发"); - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SSHChanelType::getCode).toArray(); - - /** - * 状态值 - */ - private final Integer 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/handler/MachineWebSocketHandler.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java deleted file mode 100644 index 42a18762..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/handler/MachineWebSocketHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -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 6aa2eb92..23c1f5d0 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 @@ -4,23 +4,21 @@ 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; public interface MachineInfoService { /** * 新增机器 + * * @return 新增机器的id */ Long createMachine(MachineInfoVO MachineInfoVO); /** * 查看机器列表 + * * @return 机器列表 */ PageResult listMachineInfo(@Valid MachineInfoVO MachineInfoVO); @@ -42,89 +40,30 @@ public interface MachineInfoService { /** * 批量删除机器 + * * @param machineInfoIds 机器id列表 */ void deleteMachineInfoList(String machineInfoIds); /** * 删除机器信息 + * * @param machineInfoId 机器id */ void deleteMachineInfo(Long machineInfoId); /** * 获取绑定的密钥 + * * @param secretKeyId 密钥id * @return 绑定的机器列表 */ - ListselectBindMachineBySecretKey(Long secretKeyId); - + List selectBindMachineBySecretKey(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 file 上传文件 - * @param remoteFilePath 远程文件路径 - * @return 操作结果 - */ - boolean uploadFile(MultipartFile file, String remoteFilePath); - - /** - * 从远程机器下载文件 - * - * @param remoteFilePath 远程文件路径 - * @return 操作结果 - */ - boolean downloadFile(String remoteFilePath, HttpServletResponse httpServletResponse); - - /** - * 校验机器是否存在 + * 验证机器是否存在 * * @param id 机器id */ MachineInfoDO validateMachineInfoExists(Long id); - - /** - * 根据路径获得远程文件树 - * - * @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/impl/MachineInfoServiceImpl.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/service/impl/MachineInfoServiceImpl.java index 3099f9fa..ff82abbe 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,35 +1,23 @@ 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.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; 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 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; import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; @@ -47,12 +35,6 @@ public class MachineInfoServiceImpl implements MachineInfoService { @Resource private MachineInfoMapper machineInfoMapper; - @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); @@ -95,7 +77,7 @@ public class MachineInfoServiceImpl implements MachineInfoService { @Override public void bindingSecretKey(SecretKeyVO secretKeyVO) { - machineInfoMapper.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId(),secretKeyVO.getEnableBind()); + machineInfoMapper.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId(), secretKeyVO.getEnableBind()); } @Override @@ -121,71 +103,6 @@ public class MachineInfoServiceImpl implements MachineInfoService { 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 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) { if (machineInfoVO.getHostIp().isEmpty()) { @@ -250,53 +167,10 @@ 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 boolean deleteRemoteFile(String remoteFilePath) { - fileTreeComponent.deleteRemoteFile(remoteFilePath); - return true; - } - @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); - } - } - - @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/utils/AliYunOssClient.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/AliYunOssClient.java deleted file mode 100644 index dbe11d4f..00000000 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/AliYunOssClient.java +++ /dev/null @@ -1,11 +0,0 @@ -package cd.casic.module.machine.utils; - -import cd.casic.module.infra.framework.file.core.client.s3.S3FileClient; -import cd.casic.module.infra.framework.file.core.client.s3.S3FileClientConfig; - - -public class AliYunOssClient extends S3FileClient { - public AliYunOssClient(Long id, S3FileClientConfig config) { - super(id, config); - } -} diff --git a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/CryptogramUtil.java b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/CryptogramUtil.java index 46a52e7a..0a91bfd1 100644 --- a/modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/CryptogramUtil.java +++ b/modules/module-ci-machine/src/main/java/cd/casic/module/machine/utils/CryptogramUtil.java @@ -16,11 +16,11 @@ public class CryptogramUtil { /** * 加密方法(Sm2 的专门针对前后端分离,非对称秘钥对的方式,暴露出去的公钥,对传输过程中的密码加个密) * - * @author yubaoshan * @param str 待加密数据 * @return 加密后的密文 + * @author yubaoshan */ - public static String doSm2Encrypt (String str) throws ScriptException { + public static String doSm2Encrypt(String str) throws ScriptException { return Sm2.doEncrypt(str, Keypair.PUBLIC_KEY); } @@ -28,11 +28,11 @@ public class CryptogramUtil { * 解密方法 * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可 * - * @author yubaoshan * @param str 密文 * @return 解密后的明文 + * @author yubaoshan */ - public static String doSm2Decrypt (String str) throws ScriptException { + public static String doSm2Decrypt(String str) throws ScriptException { // 解密 return Sm2.doDecrypt(str, Keypair.PRIVATE_KEY); } @@ -40,11 +40,11 @@ public class CryptogramUtil { /** * 加密方法 * - * @author yubaoshan * @param str 待加密数据 * @return 加密后的密文 + * @author yubaoshan */ - public static String doEncrypt (String str) throws ScriptException { + public static String doEncrypt(String str) throws ScriptException { // SM4 加密 cbc模式 Sm4Options sm4Options4 = new Sm4Options(); sm4Options4.setMode("cbc"); @@ -56,16 +56,16 @@ public class CryptogramUtil { * 解密方法 * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可 * - * @author yubaoshan * @param str 密文 * @return 解密后的明文 + * @author yubaoshan */ - public static String doDecrypt (String str) throws ScriptException { + public static String doDecrypt(String str) throws ScriptException { // 解密,cbc 模式,输出 utf8 字符串 Sm4Options sm4Options8 = new Sm4Options(); sm4Options8.setMode("cbc"); sm4Options8.setIv("fedcba98765432100123456789abcdef"); - String docString = Sm4.decrypt(str, Keypair.KEY, sm4Options8); + String docString = Sm4.decrypt(str, Keypair.KEY, sm4Options8); if (docString.isEmpty()) { log.warn(">>> 字段解密失败,返回原文值:{}", str); return str; @@ -77,34 +77,34 @@ public class CryptogramUtil { /** * 纯签名 * - * @author yubaoshan * @param str 待签名数据 * @return 签名结果 + * @author yubaoshan */ - public static String doSignature (String str) throws ScriptException { + public static String doSignature(String str) throws ScriptException { return Sm2.doSignature(str, Keypair.PRIVATE_KEY); } /** * 验证签名结果 * - * @author yubaoshan * @param originalStr 签名原文数据 - * @param str 签名结果 + * @param str 签名结果 * @return 是否通过 + * @author yubaoshan */ - public static boolean doVerifySignature (String originalStr, String str) throws ScriptException { + public static boolean doVerifySignature(String originalStr, String str) throws ScriptException { return Sm2.doVerifySignature(originalStr, str, Keypair.PUBLIC_KEY); } /** * 通过杂凑算法取得hash值,用于做数据完整性保护 * - * @author yubaoshan * @param str 字符串 * @return hash 值 + * @author yubaoshan */ - public static String doHashValue (String str) throws ScriptException { + public static String doHashValue(String str) throws ScriptException { return Sm3.sm3(str); }