package com.sshmanager.service; import com.sshmanager.dto.ConnectionCreateRequest; import com.sshmanager.dto.ConnectionDto; import com.sshmanager.entity.Connection; import com.sshmanager.exception.AccessDeniedException; import com.sshmanager.exception.InvalidOperationException; import com.sshmanager.exception.NotFoundException; import com.sshmanager.repository.ConnectionRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @Service @SuppressWarnings("null") public class ConnectionService { private final ConnectionRepository connectionRepository; private final EncryptionService encryptionService; private final SshService sshService; private final SshBootstrapService sshBootstrapService; public ConnectionService(ConnectionRepository connectionRepository, EncryptionService encryptionService, SshService sshService, SshBootstrapService sshBootstrapService) { this.connectionRepository = connectionRepository; this.encryptionService = encryptionService; this.sshService = sshService; this.sshBootstrapService = sshBootstrapService; } public List listByUserId(Long userId) { return connectionRepository.findByUserIdOrderByUpdatedAtDesc(userId).stream() .map(ConnectionDto::fromEntity) .collect(Collectors.toList()); } public ConnectionDto getById(Long id, Long userId) { Connection conn = connectionRepository.findById(id).orElseThrow( () -> new NotFoundException("Connection not found: " + id)); if (!conn.getUserId().equals(userId)) { throw new AccessDeniedException("Access denied"); } return ConnectionDto.fromEntity(conn); } @Transactional public ConnectionDto create(ConnectionCreateRequest request, Long userId) { validateCreateRequest(request); Connection conn = new Connection(); conn.setUserId(userId); conn.setName(trimToNull(request.getName())); conn.setHost(trimToNull(request.getHost())); conn.setPort(request.getPort() != null ? request.getPort() : 22); conn.setUsername(trimToNull(request.getUsername())); if (getSetupMode(request) == ConnectionCreateRequest.SetupMode.PASSWORD_BOOTSTRAP) { SshBootstrapService.BootstrapResult bootstrapResult = sshBootstrapService.bootstrapWithPassword(request, userId); conn.setAuthType(Connection.AuthType.PRIVATE_KEY); conn.setEncryptedPassword(null); conn.setEncryptedPrivateKey(encryptionService.encrypt(bootstrapResult.getPrivateKey())); conn.setPassphrase(null); } else { conn.setAuthType(resolveAuthType(request)); applyCredentialUpdate(conn, request); } conn = connectionRepository.save(conn); return ConnectionDto.fromEntity(conn); } @Transactional public ConnectionDto update(Long id, ConnectionCreateRequest request, Long userId) { Connection conn = connectionRepository.findById(id).orElseThrow( () -> new NotFoundException("Connection not found: " + id)); if (!conn.getUserId().equals(userId)) { throw new AccessDeniedException("Access denied"); } if (getSetupMode(request) == ConnectionCreateRequest.SetupMode.PASSWORD_BOOTSTRAP) { throw new InvalidOperationException("编辑连接时不支持一键免密配置"); } if (request.getName() != null) conn.setName(trimToNull(request.getName())); if (request.getHost() != null) conn.setHost(trimToNull(request.getHost())); if (request.getPort() != null) { validatePort(request.getPort()); conn.setPort(request.getPort()); } if (request.getUsername() != null) conn.setUsername(trimToNull(request.getUsername())); if (request.getAuthType() != null) conn.setAuthType(request.getAuthType()); validatePersistedFields(conn); applyCredentialUpdate(conn, request); validateStoredCredentials(conn); conn.setUpdatedAt(Instant.now()); conn = connectionRepository.save(conn); return ConnectionDto.fromEntity(conn); } @Transactional public void delete(Long id, Long userId) { Connection conn = connectionRepository.findById(id).orElse(null); if (conn == null) { return; } if (!conn.getUserId().equals(userId)) { throw new AccessDeniedException("Access denied"); } connectionRepository.delete(conn); } public Connection getConnectionForSsh(Long id, Long userId) { Connection conn = connectionRepository.findById(id).orElseThrow( () -> new NotFoundException("Connection not found: " + id)); if (!conn.getUserId().equals(userId)) { throw new AccessDeniedException("Access denied"); } return conn; } public String getDecryptedPassword(Connection conn) { return conn.getEncryptedPassword() != null ? encryptionService.decrypt(conn.getEncryptedPassword()) : null; } public String getDecryptedPrivateKey(Connection conn) { return conn.getEncryptedPrivateKey() != null ? encryptionService.decrypt(conn.getEncryptedPrivateKey()) : null; } public String getDecryptedPassphrase(Connection conn) { return conn.getPassphrase() != null ? encryptionService.decrypt(conn.getPassphrase()) : null; } public Connection testConnection(Connection conn, String password, String privateKey, String passphrase) { SshService.SshSession session = null; try { session = sshService.createShellSession(conn, password, privateKey, passphrase); return conn; } catch (Exception e) { throw new RuntimeException("Connection test failed: " + e.getMessage(), e); } finally { if (session != null) { session.disconnect(); } } } private void validateCreateRequest(ConnectionCreateRequest request) { if (request == null) { throw new InvalidOperationException("连接信息不能为空"); } validatePersistedFields(request); if (getSetupMode(request) == ConnectionCreateRequest.SetupMode.PASSWORD_BOOTSTRAP) { requireText(request.getBootstrapPassword(), "启用一键免密配置时必须填写初始登录密码"); return; } Connection.AuthType authType = resolveAuthType(request); if (authType == Connection.AuthType.PASSWORD) { if (!hasText(request.getPassword())) { throw new InvalidOperationException("请填写密码"); } return; } if (!hasText(request.getPrivateKey())) { throw new InvalidOperationException("请填写私钥"); } } private void validatePersistedFields(ConnectionCreateRequest request) { requireText(request.getName(), "请填写名称"); requireText(request.getHost(), "请填写主机"); requireText(request.getUsername(), "请填写用户名"); validatePort(request.getPort() != null ? request.getPort() : 22); } private void validatePersistedFields(Connection conn) { requireText(conn.getName(), "请填写名称"); requireText(conn.getHost(), "请填写主机"); requireText(conn.getUsername(), "请填写用户名"); validatePort(conn.getPort() != null ? conn.getPort() : 22); } private void applyCredentialUpdate(Connection conn, ConnectionCreateRequest request) { if (conn.getAuthType() == Connection.AuthType.PASSWORD) { String password = trimToNull(request.getPassword()); if (password != null) { conn.setEncryptedPassword(encryptionService.encrypt(password)); } conn.setEncryptedPrivateKey(null); conn.setPassphrase(null); return; } String privateKey = trimToNull(request.getPrivateKey()); if (privateKey != null) { conn.setEncryptedPrivateKey(encryptionService.encrypt(privateKey)); } if (request.getPassphrase() != null) { conn.setPassphrase(encryptionService.encrypt(trimToNull(request.getPassphrase()))); } conn.setEncryptedPassword(null); } private void validateStoredCredentials(Connection conn) { if (conn.getAuthType() == Connection.AuthType.PASSWORD) { if (!hasText(conn.getEncryptedPassword())) { throw new InvalidOperationException("请填写密码"); } return; } if (!hasText(conn.getEncryptedPrivateKey())) { throw new InvalidOperationException("请填写私钥"); } } private Connection.AuthType resolveAuthType(ConnectionCreateRequest request) { return request.getAuthType() != null ? request.getAuthType() : Connection.AuthType.PASSWORD; } private ConnectionCreateRequest.SetupMode getSetupMode(ConnectionCreateRequest request) { if (request == null || request.getSetupMode() == null) { return ConnectionCreateRequest.SetupMode.NONE; } return request.getSetupMode(); } private void requireText(String value, String message) { if (!hasText(value)) { throw new InvalidOperationException(message); } } private void validatePort(Integer port) { if (port == null || port < 1 || port > 65535) { throw new InvalidOperationException("端口号必须在1-65535之间"); } } private boolean hasText(String value) { return value != null && !value.trim().isEmpty(); } private String trimToNull(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } }