From cca2fd8c4ae07bc463e76d1d65d7aa011e8be7a4 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 5 Dec 2025 17:21:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0SSH=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ISSHAuditLogRepository.java | 5 + .../deploy/service/ISSHAuditLogService.java | 9 ++ .../service/impl/SSHAuditLogServiceImpl.java | 97 +++++++++++++------ 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ISSHAuditLogRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ISSHAuditLogRepository.java index 2ea346fa..a8b15cc0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ISSHAuditLogRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/ISSHAuditLogRepository.java @@ -19,4 +19,9 @@ public interface ISSHAuditLogRepository extends IBaseRepository sessionLocks = new ConcurrentHashMap<>(); public SSHAuditLogServiceImpl(ISSHAuditLogRepository auditLogRepository) { this.auditLogRepository = auditLogRepository; } + + /** + * 获取指定sessionId的锁对象(如果不存在则创建) + */ + private Object getSessionLock(String sessionId) { + return sessionLocks.computeIfAbsent(sessionId, k -> new Object()); + } @Override @Transactional(rollbackFor = Exception.class) public Long createAuditLog(Long userId, Server server, String sessionId, String clientIp, String userAgent) { - log.info("创建SSH审计日志: userId={}, serverId={}, sessionId={}", userId, server.getId(), sessionId); + // ⚠️ 关键:使用 sessionId 粒度的锁,确保同一个 sessionId 的创建操作串行执行 + Object lock = getSessionLock(sessionId); + + synchronized (lock) { + log.info("创建SSH审计日志: userId={}, serverId={}, sessionId={}", userId, server.getId(), sessionId); - SSHAuditLog auditLog = new SSHAuditLog(); - - // 用户信息 - auditLog.setUserId(userId); - User user = userService.findEntityById(userId); - if (user != null) { - auditLog.setUsername(user.getUsername()); + // ⚠️ 双重检查:在锁内再次检查是否已存在(防止并发重复创建) + SSHAuditLog existing = auditLogRepository.findBySessionId(sessionId); + if (existing != null) { + log.warn("SSH审计日志已存在,跳过创建: id={}, sessionId={}", existing.getId(), sessionId); + return existing.getId(); + } + + SSHAuditLog auditLog = new SSHAuditLog(); + + // 用户信息 + auditLog.setUserId(userId); + User user = userService.findEntityById(userId); + if (user != null) { + auditLog.setUsername(user.getUsername()); + } + + // 服务器信息 + auditLog.setServerId(server.getId()); + auditLog.setServerName(server.getServerName()); + auditLog.setServerIp(server.getHostIp()); + + // 会话信息 + auditLog.setSessionId(sessionId); + auditLog.setConnectTime(LocalDateTime.now()); + + // 客户端信息 + auditLog.setClientIp(clientIp); + auditLog.setUserAgent(userAgent); + + // 初始化 + auditLog.setCommandCount(0); + auditLog.setCommands("[]"); + auditLog.setStatus("CONNECTED"); + + SSHAuditLog saved = auditLogRepository.save(auditLog); + log.info("SSH审计日志创建成功: id={}", saved.getId()); + + return saved.getId(); } - - // 服务器信息 - auditLog.setServerId(server.getId()); - auditLog.setServerName(server.getServerName()); - auditLog.setServerIp(server.getHostIp()); - - // 会话信息 - auditLog.setSessionId(sessionId); - auditLog.setConnectTime(LocalDateTime.now()); - - // 客户端信息 - auditLog.setClientIp(clientIp); - auditLog.setUserAgent(userAgent); - - // 初始化 - auditLog.setCommandCount(0); - auditLog.setCommands("[]"); - auditLog.setStatus("CONNECTED"); - - SSHAuditLog saved = auditLogRepository.save(auditLog); - log.info("SSH审计日志创建成功: id={}", saved.getId()); - - return saved.getId(); } @Override @@ -136,6 +162,10 @@ public class SSHAuditLogServiceImpl log.info("SSH审计日志关闭: sessionId={}, status={}, duration={}秒", sessionId, status, auditLog.getDurationSeconds()); + // ⚠️ 清理锁对象,避免内存泄漏 + sessionLocks.remove(sessionId); + log.debug("清理sessionId锁对象: sessionId={}", sessionId); + } catch (Exception e) { log.error("关闭审计日志失败: sessionId={}", sessionId, e); } @@ -146,6 +176,11 @@ public class SSHAuditLogServiceImpl return auditLogRepository.countByUserIdAndDisconnectTimeIsNull(userId); } + @Override + public long countUserActiveSessionsForServer(Long userId, Long serverId) { + return auditLogRepository.countByUserIdAndServerIdAndDisconnectTimeIsNull(userId, serverId); + } + /** * 解析命令JSON */