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 */