Compare commits

..

No commits in common. "eb7e756fd779fb4cf6b6255c8de28d4821b40c19" and "28ed6b5c92fc17bf02f2c3ec76271d03eeb46fdc" have entirely different histories.

15 changed files with 756 additions and 833 deletions

View File

@ -7,18 +7,9 @@ import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session; import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException; import com.jcraft.jsch.SftpException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -75,7 +66,8 @@ public class FileTreeComponent {
try { try {
sftp.ls(path, selector); sftp.ls(path, selector);
} catch (SftpException e) { } catch (SftpException e) {
log.error("读取远程文件目录结构失败, 信息: {}]", e.getMessage(), e); log.error("读取远程文件目录结构失败 [错误码: {}, 信息: {}]",
e.id, e.getMessage(), e);
throw exception(READ_REMOTE_DIRECTORY_FAIL); throw exception(READ_REMOTE_DIRECTORY_FAIL);
} }
return new ArrayList<>(entries); return new ArrayList<>(entries);
@ -219,122 +211,4 @@ public class FileTreeComponent {
} }
return fileInfoMap; 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;
}
} }

View File

@ -1,220 +1,230 @@
package cd.casic.module.machine.component; //package cd.casic.module.machine.component;
//
import cd.casic.framework.commons.exception.ServiceException; //import cd.casic.framework.commons.exception.ServiceException;
import cd.casic.module.machine.dal.dataobject.MachineInfoDO; //import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.enums.AuthenticationType; //import cd.casic.module.machine.enums.AuthenticationType;
import cd.casic.module.machine.enums.ConnectionStatus; //import cd.casic.module.machine.enums.ConnectionStatus;
import cd.casic.module.machine.enums.SSHChanelType; //import cd.casic.module.machine.enums.SSHChanelType;
import cd.casic.module.machine.service.SecretKeyService; //import cd.casic.module.machine.service.SecretKeyService;
import com.jcraft.jsch.Channel; //import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec; //import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch; //import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException; //import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session; //import com.jcraft.jsch.Session;
import lombok.Data; //import lombok.Data;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; //import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; //import org.springframework.util.StringUtils;
import org.springframework.web.socket.TextMessage; //import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession; //import org.springframework.web.socket.WebSocketSession;
//
import java.io.BufferedReader; //import java.io.BufferedReader;
import java.io.IOException; //import java.io.IOException;
import java.io.InputStream; //import java.io.InputStream;
import java.io.InputStreamReader; //import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; //import java.nio.charset.StandardCharsets;
import java.util.Properties; //import java.util.Properties;
//
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; //import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; //import static cd.casic.module.machine.contants.MachineErrorCodeConstants.SECRET_KEY_NULL;
//import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
@Slf4j //
@Data //@Slf4j
@Component //@Data
public class WebSocketConnection { //@Component
private SecretKeyService secretKeyService; //public class WebSocketConnection {
private MachineInfoDO machineInfo; // private SecretKeyService secretKeyService;
private ConnectionStatus connectionStatus = ConnectionStatus.DISCONNECTED; //
private Session sshSession; // private MachineInfoDO machineInfo;
// private ConnectionStatus connectionStatus = ConnectionStatus.DISCONNECTED;
public WebSocketConnection(SecretKeyService secretKeyService) { // private Session sshSession;
this.secretKeyService = secretKeyService; //
} // public WebSocketConnection(SecretKeyService secretKeyService) {
// this.secretKeyService = secretKeyService;
private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒) // }
//
public void initConnection(MachineInfoDO machineInfo) { // private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒)
try { //
this.machineInfo = machineInfo; // public void initConnection(MachineInfoDO machineInfo) {
this.sshSession = doConnect(machineInfo); // try {
log.info("已成功建立 SSH 连接至 {} ", machineInfo.getHostIp()); // this.machineInfo = machineInfo;
this.connectionStatus = ConnectionStatus.CONNECTING; // this.sshSession = doConnect(machineInfo);
} catch (ServiceException e) { // log.info("已成功建立 SSH 连接至 {} ", machineInfo.getHostIp());
log.warn("SSH 连接失败: {}", e.getMessage()); // this.connectionStatus = ConnectionStatus.CONNECTING;
throw e; // } catch (ServiceException e) {
} // log.warn("SSH 连接失败: {}", e.getMessage());
} // throw e;
// }
public void disconnect() { // }
if (sshSession != null && sshSession.isConnected()) { //
try { // public void disconnect() {
sshSession.disconnect(); // if (sshSession != null && sshSession.isConnected()) {
log.info("SSH连接关闭: {}", machineInfo.getHostIp()); // try {
} catch (Exception e) { // sshSession.disconnect();
log.error("关闭SSH连接失败: {}", e.getMessage()); // log.info("SSH连接关闭: {}", machineInfo.getHostIp());
throw exception(CLOSE_CLOSE_SESSION_ERROR); // } catch (Exception e) {
} // log.error("关闭SSH连接失败: {}", e.getMessage());
} // throw exception(CLOSE_CLOSE_SESSION_ERROR);
connectionStatus = ConnectionStatus.DISCONNECTED; // }
} // }
// connectionStatus = ConnectionStatus.DISCONNECTED;
/** // }
* 执行远程命令支持超时和中断处理 //
*/ // /**
public void executeCommand(WebSocketSession webSocketSession, String command) { // * 执行远程命令支持超时和中断处理
// 1. 检查连接状态 // */
if (sshSession == null || !sshSession.isConnected()) { // public void executeCommand(WebSocketSession webSocketSession, String command) {
sendErrorMessage(webSocketSession, "SSH连接未建立或已断开"); // // 1. 检查连接状态
return; // if (sshSession == null || !sshSession.isConnected()) {
} // sendErrorMessage(webSocketSession, "SSH连接未建立或已断开");
try { // return;
// 2. 创建SSH命令执行通道 // }
Channel channel; // try {
try { // // 2. 创建SSH命令执行通道
channel = sshSession.openChannel(SSHChanelType.EXEC.getMessage()); // Channel channel;
} catch (JSchException e) { // try {
throw exception(CREATE_CHANEL_ERROR); // channel = sshSession.openChannel(SSHChanelType.EXEC.getMessage());
} // } catch (JSchException e) {
((ChannelExec) channel).setCommand(command); // throw exception(CREATE_CHANEL_ERROR);
// 3. 设置输入/输出流 // }
channel.setInputStream(null); // ((ChannelExec) channel).setCommand(command);
((ChannelExec) channel).setErrStream(System.err); // // 3. 设置输入/输出流
// 4. 获取命令输出流 // channel.setInputStream(null);
InputStream inputStream = channel.getInputStream(); // ((ChannelExec) channel).setErrStream(System.err);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); // // 4. 获取命令输出流
BufferedReader reader = new BufferedReader(inputStreamReader); // InputStream inputStream = channel.getInputStream();
// 5. 连接并执行命令 // InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
channel.connect(); // BufferedReader reader = new BufferedReader(inputStreamReader);
// 6. 读取命令输出并实时发送给客户端 // // 5. 连接并执行命令
String line; // channel.connect();
while ((line = reader.readLine()) != null) { // // 6. 读取命令输出并实时发送给客户端
// 实时发送输出到客户端 // String line;
webSocketSession.sendMessage(new TextMessage(line)); // while ((line = reader.readLine()) != null) {
} // // 实时发送输出到客户端
// 7. 等待命令执行完成 // webSocketSession.sendMessage(new TextMessage(line));
int exitStatus = channel.getExitStatus(); // }
// 8. 发送命令执行完毕的消息 // // 7. 等待命令执行完成
webSocketSession.sendMessage(new TextMessage( // int exitStatus = channel.getExitStatus();
"[系统] 命令执行完毕,退出状态: " + exitStatus // // 8. 发送命令执行完毕的消息
)); // webSocketSession.sendMessage(new TextMessage(
// 9. 关闭通道 // "[系统] 命令执行完毕,退出状态: " + exitStatus
channel.disconnect(); // ));
} catch (JSchException | IOException e) { // // 9. 关闭通道
throw exception(EXECUTE_COMMAND_FAIL); // channel.disconnect();
} // } catch (JSchException | IOException e) {
} // throw exception(EXECUTE_COMMAND_FAIL);
// }
// 发送错误消息的辅助方法 // }
public void sendErrorMessage(WebSocketSession webSocketSession, String message) { //
try { // // 发送错误消息的辅助方法
if (webSocketSession.isOpen()) { // public void sendErrorMessage(WebSocketSession webSocketSession, String message) {
webSocketSession.sendMessage(new TextMessage("[错误] " + message)); // try {
} // if (webSocketSession.isOpen()) {
} catch (IOException e) { // webSocketSession.sendMessage(new TextMessage("[错误] " + message));
log.error("发送错误消息失败", e); // }
throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); // } catch (IOException e) {
} // log.error("发送错误消息失败", e);
} // throw exception(WEBSOCKET_SEND_MESSAGE_ERROR);
// }
/** // }
* 实际执行连接逻辑 //
*/ // /**
private Session doConnect(MachineInfoDO machineInfo) { // * 实际执行连接逻辑
JSch jsch = new JSch(); // */
// 配置认证方式 // private Session doConnect(MachineInfoDO machineInfo) {
configureAuthentication(jsch, machineInfo); // JSch jsch = new JSch();
Session session; // // 配置认证方式
// 创建SSH会话 // configureAuthentication(jsch, machineInfo);
try { // Session session;
session = jsch.getSession( // // 创建SSH会话
machineInfo.getUsername(), // try {
machineInfo.getHostIp(), // session = jsch.getSession(
machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22 // machineInfo.getUsername(),
); // machineInfo.getHostIp(),
} catch (JSchException e) { // machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22
throw exception(CREATE_SESSION_ERROR); // );
} // } catch (JSchException e) {
// 配置连接参数 // throw exception(CREATE_SESSION_ERROR);
configureSession(session, machineInfo); // }
// 建立连接 // // 配置连接参数
try { // configureSession(session, machineInfo);
session.connect(CONNECTION_TIMEOUT); // // 建立连接
} catch (JSchException e) { // try {
throw exception(SESSION_CONNECT_ERROR); // session.connect(CONNECTION_TIMEOUT);
} // } catch (JSchException e) {
return session; // throw exception(SESSION_CONNECT_ERROR);
} // }
// return session;
/** // }
* 配置认证方式密码或密钥 //
*/ //// /**
private void configureAuthentication(JSch jsch, MachineInfoDO machineInfo) { //// * 配置认证方式密码或密钥
if (machineInfo.getAuthenticationType() == AuthenticationType.SECRET_KEY.getCode()) { //// */
// 密钥认证 //// private void configureAuthentication(JSch jsch, MachineInfoDO machineInfo) {
if (machineInfo.getSecretKeyId() == null) { //// if (machineInfo.getAuthenticationType() == AuthenticationType.SECRET_KEY.getCode()) {
throw exception(SECRET_KEY_NULL); //// // 密钥认证
} //// if (machineInfo.getSecretKeyId() == null) {
String pubKeyContent = secretKeyService.getPublicKeyContent(machineInfo.getSecretKeyId()); //// throw exception(SECRET_KEY_NULL);
// 验证秘钥格式 //// }
if (!pubKeyContent.startsWith("-----BEGIN")) { //// String pubKeyContent = secretKeyService.getKeyContent(machineInfo.getSecretKeyId());
log.error("无效的密钥格式{}", pubKeyContent); //// // 验证秘钥格式
throw exception(INVALID_kEY_FORMAT); //// if (!pubKeyContent.startsWith("-----BEGIN")) {
} //// log.error("无效的密钥格式{}", pubKeyContent);
try { //// throw exception(INVALID_kEY_FORMAT);
// 尝试加载秘钥私钥 //// }
jsch.addIdentity( //// try {
machineInfo.getName(), //// // 尝试加载秘钥私钥
pubKeyContent.getBytes(StandardCharsets.UTF_8), //// jsch.addIdentity(
null, //// machineInfo.getName(),
null //// pubKeyContent.getBytes(StandardCharsets.UTF_8),
); //// null,
log.info("密钥加载成功 {}", machineInfo.getHostIp()); //// null
} catch (JSchException e) { //// );
log.error("密钥加载失败: {}", e.getMessage()); //// log.info("密钥加载成功 {}", machineInfo.getHostIp());
throw exception(READ_SECRET_CONTENT_ERROR); //// } catch (JSchException e) {
} //// log.error("密钥加载失败: {}", e.getMessage());
} else if (machineInfo.getAuthenticationType() == AuthenticationType.PASSWORD.getCode()) { //// throw exception(READ_SECRET_CONTENT_ERROR);
// 密码认证 //// }
if (!StringUtils.hasText(machineInfo.getPassword())) { //// } else if (machineInfo.getAuthenticationType() == AuthenticationType.PASSWORD.getCode()) {
throw exception(PASSWORD_NOT_EXISTS); //// // 密码认证
} //// if (!StringUtils.hasText(machineInfo.getPassword())) {
} else { //// throw exception(PASSWORD_NOT_EXISTS);
log.error("不支持该验证类型:{}", machineInfo.getAuthenticationType()); //// }
throw exception(NOT_SUPPORT_AUTHENTICATION_TYPE); //// } else {
} //// log.error("不支持该验证类型:{}", machineInfo.getAuthenticationType());
} //// throw exception(NOT_SUPPORT_AUTHENTICATION_TYPE);
//// }
/** //// }
* 配置SSH会话参数安全增强 //
*/ // /**
private void configureSession(Session session, MachineInfoDO machineInfo) { // * 配置SSH会话参数安全增强
Properties config = new Properties(); // */
// 根据认证类型配置不同的认证策略 // private void configureSession(Session session, MachineInfoDO machineInfo) {
if (machineInfo.getAuthenticationType() == 1) { // 密码认证 // Properties config = new Properties();
// 设置密码 // // 根据认证类型配置不同的认证策略
session.setPassword(machineInfo.getPassword()); // if (machineInfo.getAuthenticationType() == 1) { // 密码认证
config.put("StrictHostKeyChecking", "no"); // // 设置密码
// 仅使用密码认证禁用其他认证方式 // session.setPassword(machineInfo.getPassword());
config.put("PreferredAuthentications", "password"); // config.put("StrictHostKeyChecking", "no");
// 禁用公钥相关配置避免干扰 // // 仅使用密码认证禁用其他认证方式
config.put("PubkeyAuthentication", "no"); // config.put("PreferredAuthentications", "password");
} else { // 密钥认证 // // 禁用公钥相关配置避免干扰
// 保持默认认证顺序公钥优先 // config.put("PubkeyAuthentication", "no");
config.put("PreferredAuthentications", "publicKey,password,keyboard-interactive"); // } else { // 密钥认证
} // try {
config.put("ServerAliveInterval", "30"); // 每30秒发送一次心跳 // String preKeyPath = secretKeyService.getSecretKey(machineInfo.getSecretKeyId()).getPath();
config.put("ServerAliveCountMax", "3"); // 允许3次心跳失败 // JSch jsch = new JSch();
session.setConfig(config); // 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);
// }
//
//}

View File

@ -1,70 +1,114 @@
package cd.casic.module.machine.component; //package cd.casic.module.machine.component;
//
import org.springframework.stereotype.Component; //import org.springframework.stereotype.Component;
//import org.springframework.web.socket.WebSocketSession;
import java.util.concurrent.ConcurrentHashMap; //
//import java.util.Collection;
@Component("machineWebSocketSessionManger") //import java.util.concurrent.ConcurrentHashMap;
//管理webSocketSession //
public class WebSocketSessionManager { //@Component("machineWebSocketSessionManger")
////管理webSocketSession
//webSocketSessionId - WebSocketConnection 与远程机器的会话管理 //public class WebSocketSessionManager {
private static final ConcurrentHashMap<String, WebSocketConnection> sessionConnectionMap = new ConcurrentHashMap<>(); //
// //webSocketSessionId - WebSocketSession 保存 WebSocketSession 对象与会话 ID 的映射
//机器id - WebSocketConnection // private static final ConcurrentHashMap<String, WebSocketSession> WebSocketSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Long, WebSocketConnection> webSocketSessionConnectionMap = new ConcurrentHashMap<>(); //
// //webSocketSessionId - WebSocketConnection 与远程机器的会话管理
public static void addWebSocketConnection(String sessionId, WebSocketConnection connection) { // private static final ConcurrentHashMap<String, WebSocketConnection> sessionConnectionMap = new ConcurrentHashMap<>();
sessionConnectionMap.put(sessionId, connection); //
} // //机器id - WebSocketConnection
// private static final ConcurrentHashMap<Long, WebSocketConnection> webSocketSessionConnectionMap = new ConcurrentHashMap<>();
/** //
* 获取 WebSocketConnection // public static void addWebSocketSession(String sessionId, WebSocketSession session) {
*/ // WebSocketSessionMap.put(sessionId, session);
public static WebSocketConnection getWebSocketConnection(String sessionId) { // }
return sessionConnectionMap.get(sessionId); //
} // /**
// * 获取 WebSocketSession
/** // */
* 移除 WebSocketConnection // public static WebSocketSession getWebSocketSession(String sessionId) {
*/ // return WebSocketSessionMap.get(sessionId);
public static void removeWebSocketConnection(String sessionId) { // }
sessionConnectionMap.remove(sessionId); //
} // /**
// * 移除 WebSocketSession
/** // */
* 获取所有 WebSocketConnection // public static void removeWebSocketSession(String sessionId) {
*/ // WebSocketSessionMap.remove(sessionId);
public static ConcurrentHashMap<Long, WebSocketConnection> getAllWebSocketConnections() { // }
return webSocketSessionConnectionMap; //
} // /**
// * 检查 sessionId 是否存在
/** // */
* 添加 WebSocketConnection // public static boolean containsWebSocketSession(String sessionId) {
*/ // return WebSocketSessionMap.containsKey(sessionId);
public static void addWebSocketConnectionByMachineId(Long machineId, WebSocketConnection connection) { // }
webSocketSessionConnectionMap.put(machineId, connection); //
} // /**
// * 获取所有 WebSocketSession
/** // */
* 获取 WebSocketConnection // public static Collection<WebSocketSession> getAllWebSocketSessions() {
*/ // return WebSocketSessionMap.values();
public static WebSocketConnection getWebSocketConnectionByMachineId(Long machineId) { // }
return webSocketSessionConnectionMap.get(machineId); //
} // public static void addWebSocketConnection(String sessionId, WebSocketConnection connection) {
// sessionConnectionMap.put(sessionId, connection);
/** // }
* 移除 WebSocketConnection //
*/ // /**
public static void removeWebSocketConnectionByMachineId(Long machineId) { // * 获取 WebSocketConnection
webSocketSessionConnectionMap.remove(machineId); // */
} // public static WebSocketConnection getWebSocketConnection(String sessionId) {
// return sessionConnectionMap.get(sessionId);
/** // }
* 检查 machineId 是否存在 //
*/ // /**
public static boolean containsMachineId(Long machineId) { // * 移除 WebSocketConnection
return webSocketSessionConnectionMap.containsKey(machineId); // */
} // public static void removeWebSocketConnection(String sessionId) {
// sessionConnectionMap.remove(sessionId);
} // }
//
// /**
// * 检查 sessionId 是否存在
// */
// public static boolean containsWebSocketConnection(String sessionId) {
// return sessionConnectionMap.containsKey(sessionId);
// }
//
// /**
// * 获取所有 WebSocketConnection
// */
// public static ConcurrentHashMap<Long, WebSocketConnection> 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);
// }
//
//}

View File

@ -1,40 +1,40 @@
package cd.casic.module.machine.configuration; //package cd.casic.module.machine.configuration;
//
import cd.casic.module.machine.Interceptor.WebSocketHandshakeInterceptor; //import cd.casic.module.machine.Interceptor.WebSocketHandshakeInterceptor;
import cd.casic.module.machine.handler.MachineWebSocketHandler; //import cd.casic.module.machine.handler.MachineWebSocketHandler;
import jakarta.annotation.Resource; //import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean; //import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; //import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket; //import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer; //import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; //import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; //import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; //import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
//
@Configuration //@Configuration
@EnableWebSocket //@EnableWebSocket
//WebSocket端点配置 ////WebSocket端点配置
public class WebSocketConfig implements WebSocketConfigurer { //public class WebSocketConfig implements WebSocketConfigurer {
@Resource // @Resource
private MachineWebSocketHandler machineWebSocketHandler; // private MachineWebSocketHandler machineWebSocketHandler;
//
@Resource // @Resource
private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor; // private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor;
//
@Override // @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(machineWebSocketHandler, "/ssh/terminal") // registry.addHandler(machineWebSocketHandler, "/ssh/terminal")
.addInterceptors(webSocketHandshakeInterceptor) // .addInterceptors(webSocketHandshakeInterceptor)
.setAllowedOrigins("*"); // 允许跨域生产环境需限制 // .setAllowedOrigins("*"); // 允许跨域生产环境需限制
} // }
//
@Bean // @Bean
public ServletServerContainerFactoryBean createWebSocketContainer() { // public ServletServerContainerFactoryBean createWebSocketContainer() {
return new ServletServerContainerFactoryBean(); // return new ServletServerContainerFactoryBean();
} // }
//
@Bean // @Bean
public ServerEndpointExporter serverEndpointExporter() { // public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); // return new ServerEndpointExporter();
} // }
} //}

View File

@ -19,6 +19,13 @@ public interface MachineErrorCodeConstants {
ErrorCode MACHINE_ENABLE = new ErrorCode(1_003_000_009, "机器启用中"); ErrorCode MACHINE_ENABLE = new ErrorCode(1_003_000_009, "机器启用中");
ErrorCode MACHINE_UN_ENABLE = new ErrorCode(1_003_000_010, "机器不可用"); 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 ========== // ========== 机器环境变量模块 1-003-002-000 ==========
ErrorCode MACHINE_ENV_NULL = new ErrorCode(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, "机器不存在"); ErrorCode MACHINE_ENV_NOT_EXISTS = new ErrorCode(1_003_002_001, "机器不存在");
@ -34,20 +41,28 @@ public interface MachineErrorCodeConstants {
// ========== 密钥模块 1-003-004-000 ========== // ========== 密钥模块 1-003-004-000 ==========
ErrorCode SECRET_KEY_NULL = new ErrorCode(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_NOT_EXISTS = new ErrorCode(1_003_004_001, "密钥不存在");
ErrorCode INVALID_kEY_FORMAT = new ErrorCode(1_003_004_002, "无效的密钥格式"); ErrorCode SECRET_KEY_PATH_NULL = new ErrorCode(1_003_004_002, "密钥路径为空");
ErrorCode READ_SECRET_CONTENT_ERROR = new ErrorCode(1_003_004_003, "读取密钥加载失败"); 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, "文件下载失败");
//========== 会话连接模块 1-003-006-000 ========== //========== 会话连接模块 1-003-006-000 ==========
ErrorCode SESSION_CONNECT_ERROR = new ErrorCode(1_003_006_001, "会话连接失败"); ErrorCode SESSION_CONNECT_ERROR = new ErrorCode(1_003_006_001, "会话连接失败");
ErrorCode CLOSE_CLOSE_SESSION_ERROR = new ErrorCode(1_003_006_002, "关闭连接失败"); ErrorCode CLOSE_CLOSE_SESSION_ERROR = new ErrorCode(1_003_006_002, "关闭连接失败");
ErrorCode EXECUTE_COMMAND_FAIL = new ErrorCode(1_003_006_003, "命令执行失败"); ErrorCode SESSION_NOT_CONNECT = new ErrorCode(1_003_006_003, "会话未连接");
ErrorCode PASSWORD_NOT_EXISTS = new ErrorCode(1_003_006_004, "密码不存在"); ErrorCode EXECUTE_COMMAND_FAIL = new ErrorCode(1_003_006_004, "命令执行失败");
ErrorCode NOT_SUPPORT_AUTHENTICATION_TYPE = new ErrorCode(1_003_006_005, "认证类型不支持"); ErrorCode PASSWORD_NOT_EXISTS = new ErrorCode(1_003_006_005, "密码不存在");
ErrorCode CREATE_SESSION_ERROR = new ErrorCode(1_003_006_006, "创建会话失败"); ErrorCode SSH_KEY_CONFIGURATION_FAIL = new ErrorCode(1_003_006_006, "SSH密钥配置失败");
ErrorCode WEBSOCKET_SEND_MESSAGE_ERROR = new ErrorCode(1_003_006_007, "websocket发送消息失败"); ErrorCode NOT_SUPPORT_AUTHENTICATION_TYPE = new ErrorCode(1_003_006_007, "认证类型不支持");
ErrorCode CREATE_CHANEL_ERROR = new ErrorCode(1_003_006_008, "执行通道创建失败"); ErrorCode CREATE_SESSION_ERROR = new ErrorCode(1_003_006_008, "创建会话失败");
ErrorCode CHANEL_CONNECT_FAIL = new ErrorCode(1_003_006_009, "通道连接失败"); ErrorCode WEBSOCKET_SEND_MESSAGE_ERROR = new ErrorCode(1_003_006_009, "websocket发送消息失败");
ErrorCode FAILED_TO_PARSE_URL_PARAMETERS = new ErrorCode(1_003_006_010, "解析URL参数失败"); 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参数失败");
//========== 远程文件树模块 1-003-007-000 ========== //========== 远程文件树模块 1-003-007-000 ==========
ErrorCode NOT_DIRECTORY_NODE = new ErrorCode(1_003_007_001, "非目录节点不能添加子节点"); ErrorCode NOT_DIRECTORY_NODE = new ErrorCode(1_003_007_001, "非目录节点不能添加子节点");
@ -55,10 +70,4 @@ public interface MachineErrorCodeConstants {
ErrorCode CONNECTION_LOST = new ErrorCode(1_003_007_003, "SFTP连接无效无法列出文件"); ErrorCode CONNECTION_LOST = new ErrorCode(1_003_007_003, "SFTP连接无效无法列出文件");
ErrorCode PATH_NOT_EXISTS = new ErrorCode(1_003_007_004, "路径不存在"); ErrorCode PATH_NOT_EXISTS = new ErrorCode(1_003_007_004, "路径不存在");
ErrorCode NO_PATH_PERMISSION = new ErrorCode(1_003_007_005, "无路径访问权限"); 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, "删除文件时出错");
} }

View File

@ -3,7 +3,6 @@ package cd.casic.module.machine.controller;
import cd.casic.framework.commons.pojo.CommonResult; import cd.casic.framework.commons.pojo.CommonResult;
import cd.casic.framework.commons.pojo.PageResult; import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.commons.util.object.BeanUtils; 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.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.enums.ConnectionStatus; import cd.casic.module.machine.enums.ConnectionStatus;
import cd.casic.module.machine.service.MachineInfoService; import cd.casic.module.machine.service.MachineInfoService;
@ -12,11 +11,9 @@ import cn.hutool.core.collection.CollUtil;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map; import java.util.Map;
@ -38,6 +35,7 @@ public class MachineInfoController {
return success(id); return success(id);
} }
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "编辑机器信息") @Operation(summary = "编辑机器信息")
// @PreAuthorize("@ss.hasPermission('ci:machineInfo:update')") // @PreAuthorize("@ss.hasPermission('ci:machineInfo:update')")
@ -80,60 +78,54 @@ public class MachineInfoController {
return success(true); return success(true);
} }
@PutMapping("/bindingSecretKey") // @GetMapping("/test")
@Operation(summary = "绑定/解绑密钥") // @Operation(summary = "测试机器连接")
public void bindingSecretKey(@RequestBody SecretKeyVO secretKeyVO) { // public CommonResult<Boolean> testConnection(@RequestParam("id") Long id) {
machineInfoService.bindingSecretKey(secretKeyVO); // return success(machineInfoService.testConnection(id));
} // }
//
// @GetMapping("/status")
// @Operation(summary = "获取机器连接状态")
// public CommonResult<ConnectionStatus> getConnectionStatus(@RequestParam Long id) {
// return success(machineInfoService.getConnectionStatus(id));
// }
//
// @GetMapping("/status/all")
// @Operation(summary = "获取所有机器连接状态")
// public CommonResult<Map<Long, ConnectionStatus>> getAllConnectionStatus() {
// return success(machineInfoService.getAllConnectionStatus());
// }
//
// @GetMapping("/connect")
// @Operation(summary = "建立连接")
// public CommonResult<Map<String, Object>> connect(@RequestParam Long id) {
// return success(machineInfoService.connect(id));
// }
@GetMapping("/test") // @GetMapping("/fileTreeNode")
@Operation(summary = "测试机器连接") // @Operation(summary = "获得文件树")
public CommonResult<Boolean> testConnection(@RequestParam("id") Long id) { // public CommonResult<Map<String, Object>> fileTreeNode(
return success(machineInfoService.testConnection(id)); // @RequestParam Long machineId,
} // @RequestParam(required = false, defaultValue = "/") String path
// ) {
@GetMapping("/status") // return CommonResult.success(machineInfoService.fileTreeNode(machineId, path));
@Operation(summary = "获取机器连接状态") // }
public CommonResult<ConnectionStatus> getConnectionStatus(@RequestParam Long id) { // @GetMapping("/upload")
return success(machineInfoService.getConnectionStatus(id)); // @Operation(summary = "上传文件到远程机器")
} // public CommonResult<Boolean> uploadFile(
// @RequestParam String sessionId,
@GetMapping("/status/all") // @RequestParam String localFilePath,
@Operation(summary = "获取所有机器连接状态") // @RequestParam String remoteFilePath
public CommonResult<Map<Long, ConnectionStatus>> getAllConnectionStatus() { // ) {
return success(machineInfoService.getAllConnectionStatus()); // return success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath));
} // }
//
@GetMapping("/connect") // @GetMapping("/download")
@Operation(summary = "建立连接") // @Operation(summary = "从远程机器下载文件")
public CommonResult<Map<String, Object>> connect(@RequestParam Long id) { // public CommonResult<Boolean> downloadFile(
return success(machineInfoService.connect(id)); // @RequestParam String sessionId,
} // @RequestParam String remoteFilePath,
// @RequestParam String localFilePath) {
@GetMapping("/fileTreeNode") // return success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath));
@Operation(summary = "获得文件树") // }
public CommonResult<Map<String, Object>> fileTreeNode(
@RequestParam Long machineId,
@RequestParam(required = false, defaultValue = "/") String path
) {
return success(machineInfoService.fileTreeNode(machineId, path));
}
@PostMapping("/upload")
@Operation(summary = "上传文件到远程机器")
public CommonResult<Boolean> uploadFile(@RequestParam MultipartFile file, @RequestParam String remoteFilePath) {
return success(machineInfoService.uploadFile(file, remoteFilePath));
}
@GetMapping("/download")
@Operation(summary = "从远程机器下载文件")
public CommonResult<Boolean> downloadFile(@RequestParam String remoteFilePath, HttpServletResponse httpServletResponse) {
return success(machineInfoService.downloadFile(remoteFilePath, httpServletResponse));
}
@DeleteMapping("/deleteRemoteFile")
@Operation(summary = "删除远程机器选定文件")
public CommonResult<Boolean> deleteRemoteFile(@RequestParam String remoteFilePath) {
return success(machineInfoService.deleteRemoteFile(remoteFilePath));
}
} }

View File

@ -12,6 +12,9 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; 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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -30,7 +33,7 @@ public class SecretKeyController {
@PostMapping(value = "/create") @PostMapping(value = "/create")
@Operation(summary = "新增密钥") @Operation(summary = "新增密钥")
// @PreAuthorize("@ss.hasPermission('ci:secretKey:create')") // @PreAuthorize("@ss.hasPermission('ci:secretKey:create')")
public CommonResult<Long> createSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) { public CommonResult<Long> createSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) throws Exception {
Long secretKeyId = secretKeyService.createSecretKey(secretKeyVO); Long secretKeyId = secretKeyService.createSecretKey(secretKeyVO);
return success(secretKeyId); return success(secretKeyId);
} }
@ -43,6 +46,21 @@ public class SecretKeyController {
return success(true); return success(true);
} }
@PutMapping("/bindingMachine")
@Operation(summary = "绑定机器")
// @PreAuthorize("@ss.hasPermission('ci:secretKey:binding')")
public CommonResult<Boolean> bindingMachine(@Valid @RequestBody SecretKeyVO secretKeyVO) {
secretKeyService.bindingMachine(secretKeyVO);
return success(true);
}
@PutMapping("/unbindMachine")
@Operation(summary = "解绑机器")
public CommonResult<Boolean> unbindMachine(@Valid @RequestBody SecretKeyVO secretKeyVO) {
secretKeyService.unbindMachine(secretKeyVO);
return success(true);
}
@GetMapping("/getBindMachine") @GetMapping("/getBindMachine")
@Operation(summary = "获取密钥绑定的机器列表") @Operation(summary = "获取密钥绑定的机器列表")
public CommonResult<List<MachineInfoDO>> getBindMachine(@RequestParam Long secretKeyId) { public CommonResult<List<MachineInfoDO>> getBindMachine(@RequestParam Long secretKeyId) {
@ -56,6 +74,13 @@ public class SecretKeyController {
return success(secretKeyVO); return success(secretKeyVO);
} }
@DeleteMapping("/delete")
@Operation(summary = "密钥信息删除")
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
secretKeyService.deleteSecretKey(id);
return success(true);
}
@DeleteMapping("/deleteList") @DeleteMapping("/deleteList")
@Operation(summary = "批量删除密钥") @Operation(summary = "批量删除密钥")
// @PreAuthorize("@ss.hasPermission('ci:secretKey:delete')") // @PreAuthorize("@ss.hasPermission('ci:secretKey:delete')")
@ -67,10 +92,16 @@ public class SecretKeyController {
@PostMapping("/list") @PostMapping("/list")
@Operation(summary = "获取密钥信息列表") @Operation(summary = "获取密钥信息列表")
public CommonResult<PageResult<SecretKeyVO>> getSecretKeyPage(@Valid @RequestBody SecretKeyVO secretKeyVO) { public CommonResult<PageResult<SecretKeyVO>> getSecretKeyPage(@Valid @RequestBody SecretKeyVO secretKeyVO) {
PageResult<SecretKeyDO> pageResult = secretKeyService.getSecretKeyPage(secretKeyVO); PageResult<SecretKeyDO> pageResult = secretKeyService.getSecretKeypage(secretKeyVO);
if (CollUtil.isEmpty(pageResult.getList())) { if (CollUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal())); return success(new PageResult<>(pageResult.getTotal()));
} }
return success(BeanUtils.toBean(pageResult, SecretKeyVO.class)); return success(BeanUtils.toBean(pageResult, SecretKeyVO.class));
} }
// @GetMapping("/download")
// @Operation(summary = "下载密钥文件")
// public ResponseEntity<InputStreamResource> downloadSecretFile(@RequestParam("id") Long id) {
// return secretKeyService.downloadSecretFile(id);
// }
} }

View File

@ -4,7 +4,9 @@ import cd.casic.framework.commons.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@ -43,6 +45,4 @@ public class SecretKeyVO extends PageParam {
@Schema(description = "公钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "******") @Schema(description = "公钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "******")
private String public_key; private String public_key;
@Schema(description = "绑定/解绑密钥",example = "true")
private Boolean enableBind;
} }

View File

@ -3,10 +3,15 @@ package cd.casic.module.machine.dal.mysql;
import cd.casic.framework.commons.pojo.PageResult; import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.mybatis.core.mapper.BaseMapperX; import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX; import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
import cd.casic.module.machine.controller.vo.MachineEnvVO;
import cd.casic.module.machine.controller.vo.MachineInfoVO; import cd.casic.module.machine.controller.vo.MachineInfoVO;
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
import cd.casic.module.machine.dal.dataobject.MachineInfoDO; import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import jakarta.annotation.Resource;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@ -23,14 +28,10 @@ public interface MachineInfoMapper extends BaseMapperX<MachineInfoDO> {
this.update(null, set); this.update(null, set);
} }
default void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId,Boolean enableBind) { default void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId) {
UpdateWrapper<MachineInfoDO> wrapper = new UpdateWrapper<>(); UpdateWrapper<MachineInfoDO> wrapper = new UpdateWrapper<>();
wrapper.in("id", machineInfoIds); wrapper.in("id", machineInfoIds) // 匹配 ID 集合
if (enableBind){ .set("secret_key_id", secretKeyId); // 设置新的 status
wrapper.set("secret_key_id", secretKeyId);// 匹配 ID 集合
}else {
wrapper.set("secret_key_id", null);
}
this.update(null, wrapper); this.update(null, wrapper);
} }

View File

@ -1,64 +1,67 @@
package cd.casic.module.machine.handler; //package cd.casic.module.machine.handler;
//
import cd.casic.module.machine.component.WebSocketConnection; ////import cd.casic.module.machine.component.WebSocketConnection;
import cd.casic.module.machine.component.WebSocketSessionManager; ////import cd.casic.module.machine.component.WebSocketSessionManager;
import cd.casic.module.machine.enums.ConnectionStatus; //import cd.casic.module.machine.enums.ConnectionStatus;
import org.jetbrains.annotations.NotNull; //import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component; //import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus; //import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage; //import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession; //import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler; //import org.springframework.web.socket.handler.TextWebSocketHandler;
//
import java.io.IOException; //import java.io.IOException;
//
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception; //import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*; //import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
//
//WebSocket处理器 ////WebSocket处理器
@Component("machineWebSocketHandler") //@Component("machineWebSocketHandler")
public class MachineWebSocketHandler extends TextWebSocketHandler { //public class MachineWebSocketHandler extends TextWebSocketHandler {
//
@Override // @Override
public void afterConnectionEstablished(@NotNull WebSocketSession webSocketSession) { // public void afterConnectionEstablished(@NotNull WebSocketSession webSocketSession) {
Long machineId = (Long) webSocketSession.getAttributes().get("machineId"); // Long machineId = (Long) webSocketSession.getAttributes().get("machineId");
//保存webSocketSession信息 // //保存webSocketSession信息
WebSocketSessionManager.addWebSocketConnection(webSocketSession.getId(), WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId)); // WebSocketSessionManager.addWebSocketSession(webSocketSession.getId(), webSocketSession);
try { // WebSocketSessionManager.addWebSocketConnection(webSocketSession.getId(), WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId));
webSocketSession.sendMessage(new TextMessage("欢迎连接到 WebSocket 服务器!")); // try {
} catch (IOException e) { // webSocketSession.sendMessage(new TextMessage("欢迎连接到 WebSocket 服务器!"));
throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); // } catch (IOException e) {
} // throw exception(WEBSOCKET_SEND_MESSAGE_ERROR);
} // }
// }
// 处理文本消息 //
@Override // // 处理文本消息
protected void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) { // @Override
String payload = message.getPayload(); // protected void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) {
String sessionId = webSocketSession.getId(); // String payload = message.getPayload();
// 从管理器获取连接 // String sessionId = webSocketSession.getId();
WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId); // // 从管理器获取连接
if (webSocketConnection != null && ConnectionStatus.CONNECTING.equals(webSocketConnection.getConnectionStatus())) { // WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId);
// 转发消息到远程机器 //
webSocketConnection.executeCommand(webSocketSession, payload); // if (webSocketConnection != null && ConnectionStatus.CONNECTING.equals(webSocketConnection.getConnectionStatus())) {
} else if (webSocketConnection != null) { // // 转发消息到远程机器
webSocketConnection.sendErrorMessage(webSocketSession, "连接已断开,无法发送消息"); // webSocketConnection.executeCommand(webSocketSession, payload);
} else { // } else if (webSocketConnection != null) {
throw exception(WEBSOCKET_SEND_MESSAGE_ERROR); // webSocketConnection.sendErrorMessage(webSocketSession, "连接已断开,无法发送消息");
} // } else {
} // throw exception(WEBSOCKET_SEND_MESSAGE_ERROR);
// }
@Override // }
public void afterConnectionClosed(WebSocketSession webSocketSession, @NotNull CloseStatus status) { //
String sessionId = webSocketSession.getId(); // @Override
// 获取并关闭相关的 WebSocketConnection // public void afterConnectionClosed(WebSocketSession webSocketSession, @NotNull CloseStatus status) {
WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId); // String sessionId = webSocketSession.getId();
if (webSocketConnection != null) { // // 获取并关闭相关的 WebSocketConnection
webSocketConnection.disconnect(); // WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId);
} // if (webSocketConnection != null) {
// 从管理器中移除会话和连接 // webSocketConnection.disconnect();
WebSocketSessionManager.removeWebSocketConnection(sessionId); // }
Long machineInfoId = (Long) webSocketSession.getAttributes().get("machineId"); // // 从管理器中移除会话和连接
WebSocketSessionManager.removeWebSocketConnectionByMachineId(machineInfoId); // WebSocketSessionManager.removeWebSocketSession(sessionId);
} // WebSocketSessionManager.removeWebSocketConnection(sessionId);
} // Long machineInfoId = (Long) webSocketSession.getAttributes().get("machineId");
// WebSocketSessionManager.removeWebSocketConnectionByMachineId(machineInfoId);
// }
//}

View File

@ -2,12 +2,9 @@ package cd.casic.module.machine.service;
import cd.casic.framework.commons.pojo.PageResult; import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.controller.vo.MachineInfoVO; 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.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.enums.ConnectionStatus; import cd.casic.module.machine.enums.ConnectionStatus;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -21,35 +18,18 @@ public interface MachineInfoService {
/** /**
* 查看机器列表 * 查看机器列表
* @return 机器列表 * @return 分院
*/ */
PageResult<MachineInfoDO> listMachineInfo(@Valid MachineInfoVO MachineInfoVO); PageResult<MachineInfoDO> listMachineInfo(@Valid MachineInfoVO MachineInfoVO);
/**
* 更新机器信息
*/
void updateMachineInfo(@Valid MachineInfoVO machineInfoVO); void updateMachineInfo(@Valid MachineInfoVO machineInfoVO);
/**
* 变更机器状态
*/
Integer updateStatus(@Valid MachineInfoVO machineInfoVO); Integer updateStatus(@Valid MachineInfoVO machineInfoVO);
/** void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId);
* 绑定密钥
*/
void bindingSecretKey(SecretKeyVO secretKeyVO);
/**
* 批量删除机器
* @param machineInfoIds 机器id列表
*/
void deleteMachineInfoList(String machineInfoIds); void deleteMachineInfoList(String machineInfoIds);
/**
* 删除机器信息
* @param machineInfoId 机器id
*/
void deleteMachineInfo(Long machineInfoId); void deleteMachineInfo(Long machineInfoId);
/** /**
@ -60,51 +40,55 @@ public interface MachineInfoService {
List<MachineInfoDO>selectBindMachineBySecretKey(Long secretKeyId); List<MachineInfoDO>selectBindMachineBySecretKey(Long secretKeyId);
/** // /**
* 测试机器连接 // * 测试机器连接
* @param id 机器id // *
* @return 连接是否成功 // * @param id 机器id
*/ // * @return 连接是否成功
boolean testConnection(Long id); // */
// boolean testConnection(Long id);
//
// /**
// * 连接远程机器
// *
// * @param id 机器id
// * @return 连接后端文件树
// */
// Map<String, Object> connect(Long id);
//
// /**
// * 获取机器连接状态
// *
// * @return 连接状态
// */
// ConnectionStatus getConnectionStatus(Long id);
//
// /**
// * 获取所有连接状态
// *
// * @return 机器名称到连接状态的映射
// */
// Map<Long, ConnectionStatus> getAllConnectionStatus();
/** // /**
* 连接远程机器 // * 上传文件到远程机器
* // *
* @param id 机器id // * @param sessionId 会话ID
* @return 连接后端文件树 // * @param localFilePath 本地文件路径
*/ // * @param remoteFilePath 远程文件路径
Map<String, Object> connect(Long id); // * @return 操作结果
// */
/** // boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath);
* 获取机器连接状态 //
* // /**
* @return 连接状态 // * 从远程机器下载文件
*/ // *
ConnectionStatus getConnectionStatus(Long id); // * @param sessionId 会话ID
// * @param remoteFilePath 远程文件路径
/** // * @param localFilePath 本地文件路径
* 获取所有连接状态 // * @return 操作结果
* // */
* @return 机器名称到连接状态的映射 // boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath);
*/
Map<Long, ConnectionStatus> getAllConnectionStatus();
/**
* 上传文件到远程机器
*
* @param file 上传文件
* @param remoteFilePath 远程文件路径
* @return 操作结果
*/
boolean uploadFile(MultipartFile file, String remoteFilePath);
/**
* 从远程机器下载文件
*
* @param remoteFilePath 远程文件路径
* @return 操作结果
*/
boolean downloadFile(String remoteFilePath, HttpServletResponse httpServletResponse);
/** /**
* 校验机器是否存在 * 校验机器是否存在
@ -113,18 +97,12 @@ public interface MachineInfoService {
*/ */
MachineInfoDO validateMachineInfoExists(Long id); MachineInfoDO validateMachineInfoExists(Long id);
/** // /**
* 根据路径获得远程文件树 // * 根据路径获得远程文件树
* // *
* @param machineId 机器id // * @param machineId 机器id
* @param path 文件夹路径 // * @param path 文件夹路径
* @return 远程文件树 // * @return 远程文件树
*/ // */
Map<String, Object> fileTreeNode(Long machineId, String path); // Map<String, Object> fileTreeNode(Long machineId, String path);
/**
* 删除远程文件路径
* @param remoteFilePath 远程文件路径
*/
boolean deleteRemoteFile(String remoteFilePath);
} }

View File

@ -4,6 +4,8 @@ import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.controller.vo.MachineProxyVO; import cd.casic.module.machine.controller.vo.MachineProxyVO;
import cd.casic.module.machine.dal.dataobject.MachineProxyDO; import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map; import java.util.Map;
@ -24,6 +26,7 @@ public interface MachineProxyService {
/** /**
* 删除代理 * 删除代理
* *
* @param
*/ */
void delete(Long id); void delete(Long id);
@ -42,8 +45,5 @@ public interface MachineProxyService {
*/ */
void deleteProxyList(String proxyIds); void deleteProxyList(String proxyIds);
/**
* 获取代理列表(分页)
*/
PageResult<MachineProxyDO> getProxyPage(@Valid MachineProxyVO machineProxyVO); PageResult<MachineProxyDO> getProxyPage(@Valid MachineProxyVO machineProxyVO);
} }

View File

@ -5,44 +5,30 @@ import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.dal.dataobject.SecretKeyDO; import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
import cd.casic.module.machine.controller.vo.SecretKeyVO; import cd.casic.module.machine.controller.vo.SecretKeyVO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ResponseEntity;
import java.util.List; import java.util.List;
public interface SecretKeyService { 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); void updateSecretKey(@Valid SecretKeyVO secretKeyVO);
/** PageResult<SecretKeyDO> getSecretKeypage(@Valid SecretKeyVO secretKeyVO);
* 获取密钥列表(分页)
*/
PageResult<SecretKeyDO> getSecretKeyPage(@Valid SecretKeyVO secretKeyVO);
/**
* 批量删除密钥
* @param ids 密钥id集合
*/
void deleteSecretKeyList(List<Long> ids); void deleteSecretKeyList(List<Long> ids);
/**
* 根据id获取密钥对象
*/
SecretKeyVO getSecretKey(Long id); SecretKeyVO getSecretKey(Long id);
/** void deleteSecretKey(Long id);
* 获取绑定的密钥的机器列表
*/ void unbindMachine(@Valid SecretKeyVO secretKeyVO);
List<MachineInfoDO> getBindMachine(Long secretKeyId); List<MachineInfoDO> getBindMachine(Long secretKeyId);
/** // String getKeyContent(Long secretKeyId);
* 获取公钥内容
*/
String getPublicKeyContent(Long secretKeyId);
} }

View File

@ -2,8 +2,7 @@ package cd.casic.module.machine.service.impl;
import cd.casic.framework.commons.pojo.PageResult; import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.module.machine.component.FileTreeComponent; 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.component.WebSocketSessionManager;
import cd.casic.module.machine.controller.vo.SecretKeyVO; import cd.casic.module.machine.controller.vo.SecretKeyVO;
import cd.casic.module.machine.enums.AuthenticationType; import cd.casic.module.machine.enums.AuthenticationType;
import cd.casic.module.machine.enums.MachineInfoType; import cd.casic.module.machine.enums.MachineInfoType;
@ -12,22 +11,17 @@ import cd.casic.module.machine.controller.vo.MachineInfoVO;
import cd.casic.module.machine.dal.dataobject.MachineInfoDO; import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.enums.ConnectionStatus; import cd.casic.module.machine.enums.ConnectionStatus;
import cd.casic.module.machine.enums.MachineInfoStatus; 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.MachineInfoService;
import cd.casic.module.machine.service.SecretKeyService; import cd.casic.module.machine.service.SecretKeyService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.jcraft.jsch.Session; import com.jcraft.jsch.Session;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import cd.casic.framework.commons.util.object.BeanUtils; import cd.casic.framework.commons.util.object.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; 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.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -50,9 +44,6 @@ public class MachineInfoServiceImpl implements MachineInfoService {
@Resource @Resource
private FileTreeComponent fileTreeComponent; private FileTreeComponent fileTreeComponent;
//todo部署后更改
private static final Path TEMP_DIR = Paths.get("D:\\桌面\\work\\ops-pro111\\temp");
@Override @Override
public Long createMachine(MachineInfoVO machineInfoVO) { public Long createMachine(MachineInfoVO machineInfoVO) {
validateMachineEnvAdd(machineInfoVO); validateMachineEnvAdd(machineInfoVO);
@ -94,8 +85,8 @@ public class MachineInfoServiceImpl implements MachineInfoService {
} }
@Override @Override
public void bindingSecretKey(SecretKeyVO secretKeyVO) { public void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId) {
machineInfoMapper.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId(),secretKeyVO.getEnableBind()); machineInfoMapper.bindingSecretKey(machineInfoIds, secretKeyId);
} }
@Override @Override
@ -118,73 +109,78 @@ public class MachineInfoServiceImpl implements MachineInfoService {
@Override @Override
public List<MachineInfoDO> selectBindMachineBySecretKey(Long secretKeyId) { public List<MachineInfoDO> selectBindMachineBySecretKey(Long secretKeyId) {
return machineInfoMapper.selectBindMachineBySecretKey(secretKeyId); return machineInfoMapper.selectBindMachineBySecretKey(secretKeyId);
} }
@Override // @Override
public boolean testConnection(Long id) { // public boolean testConnection(Long id) {
//先查询机器是否存在在判断机器可用性 // //先查询机器是否存在在判断机器可用性
MachineInfoDO machineInfoDO = validateMachineInfoExists(id); // MachineInfoDO machineInfoDO = validateMachineInfoExists(id);
validateMachineUnEnable(machineInfoDO); // validateMachineUnEnable(machineInfoDO);
log.info("测试机器连接: {}", machineInfoDO.getHostIp()); // log.info("测试机器连接: {}", machineInfoDO.getHostIp());
WebSocketConnection webSocketConnection = createWebSocketConnection(machineInfoDO); // WebSocketConnection webSocketConnection = createWebSocketConnection(machineInfoDO);
webSocketConnection.initConnection(machineInfoDO); // webSocketConnection.initConnection(machineInfoDO);
return true; // return true;
} // }
//
// @Override
// public Map<String, Object> 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<Long, ConnectionStatus> getAllConnectionStatus() {
// return WebSocketSessionManager.getAllWebSocketConnections().entrySet().stream()
// .collect(Collectors.toMap(
// Map.Entry::getKey,
// entry -> entry.getValue().getConnectionStatus()
// ));
// }
@Override // @Override
public Map<String, Object> connect(Long id) { // public boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath) {
//todo使用代理机器的情况还未完成 // log.info("上传文件: {} -> {}, 会话ID: {}", localFilePath, remoteFilePath, sessionId);
WebSocketConnection webSocketConnection = new WebSocketConnection(this.secretKeyService); // WebSocketConnection webSocketConnection = sessionConnectionMap.get(sessionId);
MachineInfoDO machineInfoDO = validateMachineInfoExists(id); // if (webSocketConnection == null||webSocketConnection.getConnectionStatus() != ConnectionStatus.CONNECTED) {
//初始化连接 // throw exception(SESSION_NOT_CONNECT);
webSocketConnection.initConnection(machineInfoDO); // }
WebSocketSessionManager.addWebSocketConnectionByMachineId(id, webSocketConnection); // try {
return fileTreeComponent.getRemoteFileTree(webSocketConnection.getSshSession(), "/"); // return webSocketConnection.uploadFile(localFilePath, remoteFilePath);
} // } catch (Exception e) {
// log.error("文件上传失败: {}", e.getMessage(), e);
// throw exception(FILE_UPLOAD_ERROR);
// }
// }
@Override // @Override
public ConnectionStatus getConnectionStatus(Long id) { // public boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath) {
validateMachineInfoExists(id); // log.info("下载文件: {} -> {}, 会话ID: {}", remoteFilePath, localFilePath, sessionId);
WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnectionByMachineId(id); //
return webSocketConnection == null ? ConnectionStatus.DISCONNECTED : webSocketConnection.getConnectionStatus(); // WebSocketConnection webSocketConnection = sessionConnectionMap.get(sessionId);
} // if (webSocketConnection == null||webSocketConnection.getConnectionStatus() != ConnectionStatus.CONNECTED) {
// throw new RuntimeException("会话不存在: " + sessionId);
@Override // }
public Map<Long, ConnectionStatus> getAllConnectionStatus() { // try {
return WebSocketSessionManager.getAllWebSocketConnections().entrySet().stream() // return webSocketConnection.downloadFile(remoteFilePath, localFilePath);
.collect(Collectors.toMap( // } catch (Exception e) {
Map.Entry::getKey, // log.error("文件下载失败: {}", e.getMessage(), e);
entry -> entry.getValue().getConnectionStatus() // throw exception(FILE_DOWNLOAD_ERROR);
)); // }
} // }
@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 @VisibleForTesting
void validateMachineEnvAdd(MachineInfoVO machineInfoVO) { void validateMachineEnvAdd(MachineInfoVO machineInfoVO) {
@ -250,18 +246,12 @@ public class MachineInfoServiceImpl implements MachineInfoService {
return machineInfoDO; return machineInfoDO;
} }
@Override // @Override
public Map<String, Object> fileTreeNode(Long machineId, String path) { // public Map<String, Object> fileTreeNode(Long machineId, String path) {
validateMachineInfoExists(machineId); // validateMachineInfoExists(machineId);
Session sshSession = WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId).getSshSession(); // Session sshSession = WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId).getSshSession();
return fileTreeComponent.getRemoteFileTree(sshSession, path); // return fileTreeComponent.getRemoteFileTree(sshSession, path);
} // }
@Override
public boolean deleteRemoteFile(String remoteFilePath) {
fileTreeComponent.deleteRemoteFile(remoteFilePath);
return true;
}
@VisibleForTesting @VisibleForTesting
void validateMachineEnable(MachineInfoDO machineInfoDO) { void validateMachineEnable(MachineInfoDO machineInfoDO) {
@ -278,25 +268,12 @@ public class MachineInfoServiceImpl implements MachineInfoService {
} }
} }
@VisibleForTesting // @VisibleForTesting
WebSocketConnection createWebSocketConnection(MachineInfoDO machineInfoDO) { // WebSocketConnection createWebSocketConnection(MachineInfoDO machineInfoDO) {
if (WebSocketSessionManager.containsMachineId(machineInfoDO.getId())) { // if (WebSocketSessionManager.containsMachineId(machineInfoDO.getId())) {
return WebSocketSessionManager.getWebSocketConnectionByMachineId((machineInfoDO.getId())); // return WebSocketSessionManager.getWebSocketConnectionByMachineId((machineInfoDO.getId()));
} else { // } else {
return new WebSocketConnection(this.secretKeyService); // 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;
}
} }

View File

@ -7,6 +7,9 @@ import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.dal.dataobject.SecretKeyDO; import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
import cd.casic.module.machine.dal.mysql.SecretKeyMapper; import cd.casic.module.machine.dal.mysql.SecretKeyMapper;
import cd.casic.module.machine.service.MachineInfoService; 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 cd.casic.module.machine.service.SecretKeyService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -30,28 +33,36 @@ public class SecretKeyServiceImpl implements SecretKeyService {
@Resource @Resource
private SecretKeyMapper secretKeyMapper; private SecretKeyMapper secretKeyMapper;
@Override
public void deleteSecretKey(Long id) {
validateSecretKeyExists(id);
secretKeyMapper.deleteById(id);
}
@Override @Override
public SecretKeyVO getSecretKey(Long id) { public SecretKeyVO getSecretKey(Long id) {
SecretKeyDO secretKeyDO = validateSecretKeyExists(id); SecretKeyDO secretKeyDO = validateSecretKeyExists(id);
return BeanUtils.toBean(secretKeyDO, SecretKeyVO.class); return BeanUtils.toBean(secretKeyDO, SecretKeyVO.class);
} }
@Override
public void unbindMachine(SecretKeyVO secretKeyVO) {
machineInfoService.bindingSecretKey(secretKeyVO.getMachineInfoIds(),null);
}
@Override @Override
public List<MachineInfoDO> getBindMachine(Long secretKeyId) { public List<MachineInfoDO> getBindMachine(Long secretKeyId) {
return machineInfoService.selectBindMachineBySecretKey(secretKeyId); return machineInfoService.selectBindMachineBySecretKey(secretKeyId);
} }
@Override
public String getPublicKeyContent(Long secretKeyId) {
return secretKeyMapper.selectById(secretKeyId).getPublicKey();
}
@Override @Override
public Long createSecretKey(SecretKeyVO secretKeyVO) { public Long createSecretKey(SecretKeyVO secretKeyVO) {
validateSecretKeyAdd(secretKeyVO); validateSecretKeyAdd(secretKeyVO);
SecretKeyDO secretKeyDO = BeanUtils.toBean(secretKeyVO, SecretKeyDO.class); SecretKeyDO secretKeyDO = BeanUtils.toBean(secretKeyVO, SecretKeyDO.class);
secretKeyMapper.insert(secretKeyDO); secretKeyMapper.insert(secretKeyDO);
return secretKeyDO.getId(); return secretKeyDO.getId();
} }
@Override @Override
@ -61,21 +72,25 @@ public class SecretKeyServiceImpl implements SecretKeyService {
secretKeyMapper.updateById(secretKeyDO); secretKeyMapper.updateById(secretKeyDO);
} }
@Override
public void bindingMachine(SecretKeyVO secretKeyVO) {
validateSecretKeyExists(secretKeyVO.getId());
machineInfoService.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId());
}
@Override @Override
@Transactional @Transactional
public void deleteSecretKeyList(List<Long> ids) { public void deleteSecretKeyList(List<Long> ids) {
SecretKeyVO secretKeyVO = new SecretKeyVO() machineInfoService.bindingSecretKey(ids,null);
.setMachineInfoIds(ids)
.setEnableBind(false);
machineInfoService.bindingSecretKey(secretKeyVO);
secretKeyMapper.deleteBatchIds(ids); secretKeyMapper.deleteBatchIds(ids);
} }
@Override @Override
public PageResult<SecretKeyDO> getSecretKeyPage(SecretKeyVO secretKeyVO) { public PageResult<SecretKeyDO> getSecretKeypage(SecretKeyVO secretKeyVO) {
return secretKeyMapper.selectPage(secretKeyVO); return secretKeyMapper.selectPage(secretKeyVO);
} }
@VisibleForTesting @VisibleForTesting
void validateSecretKeyAdd(SecretKeyVO secretKeyVO) { void validateSecretKeyAdd(SecretKeyVO secretKeyVO) {
if (secretKeyVO == null) { if (secretKeyVO == null) {
@ -83,6 +98,8 @@ public class SecretKeyServiceImpl implements SecretKeyService {
} }
} }
@VisibleForTesting @VisibleForTesting
SecretKeyDO validateSecretKeyExists(Long id) { SecretKeyDO validateSecretKeyExists(Long id) {
if (id == null) { if (id == null) {
@ -94,4 +111,5 @@ public class SecretKeyServiceImpl implements SecretKeyService {
} }
return secretKeyDO; return secretKeyDO;
} }
} }