diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/lock/SyncLockManager.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/lock/SyncLockManager.java new file mode 100644 index 00000000..87a3553e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/lock/SyncLockManager.java @@ -0,0 +1,235 @@ +package com.qqchen.deploy.backend.deploy.lock; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * 智能同步任务本地锁管理器 + *

自动识别调用者(类名+方法名)和参数,生成唯一锁key,无需手动指定锁类型

+ * + *

使用示例: + *

+ * // 加锁(自动识别调用方法和参数)
+ * if (!syncLockManager.tryLock(externalSystemId)) {
+ *     return; // 已有同步任务在执行
+ * }
+ * try {
+ *     // 执行同步逻辑
+ * } finally {
+ *     syncLockManager.unlock(externalSystemId);
+ * }
+ * 
+ * + *

锁key格式:ClassName.methodName:param1:param2:...

+ */ +@Slf4j +@Component +public class SyncLockManager { + + // 统一的锁存储:key = ClassName.methodName:param1:param2:... + private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); + + /** + * 尝试获取锁(自动识别调用者) + * + * @param params 锁参数(如 externalSystemId, viewId等) + * @return true-获取成功,false-已被锁定 + */ + public boolean tryLock(Object... params) { + CallerInfo callerInfo = getCallerInfo(); + String lockKey = buildLockKey(callerInfo, params); + boolean acquired = locks.putIfAbsent(lockKey, true) == null; + + if (!acquired) { + log.warn("同步任务正在执行中,跳过本次同步: method={}, params={}", + callerInfo.getFullMethodName(), formatParams(params)); + } else { + log.debug("获取同步锁成功: method={}, params={}", + callerInfo.getFullMethodName(), formatParams(params)); + } + + return acquired; + } + + /** + * 释放锁(自动识别调用者) + * + * @param params 锁参数(必须与tryLock时完全一致) + */ + public void unlock(Object... params) { + CallerInfo callerInfo = getCallerInfo(); + String lockKey = buildLockKey(callerInfo, params); + locks.remove(lockKey); + log.debug("同步任务完成,释放锁: method={}, params={}", + callerInfo.getFullMethodName(), formatParams(params)); + } + + /** + * 检查锁是否被持有(自动识别调用者) + * + * @param params 锁参数 + * @return true-已被锁定,false-未锁定 + */ + public boolean isLocked(Object... params) { + CallerInfo callerInfo = getCallerInfo(); + String lockKey = buildLockKey(callerInfo, params); + return locks.containsKey(lockKey); + } + + /** + * 获取当前锁数量(用于监控) + * + * @return 当前持有的锁数量 + */ + public int getLockCount() { + return locks.size(); + } + + /** + * 获取调用者信息(通过栈追踪,智能跳过Spring代理层) + * + * @return 调用者信息 + */ + private CallerInfo getCallerInfo() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + + // 从栈顶开始遍历,跳过本类和Spring的代理/拦截器类 + // 栈结构示例: + // 0: Thread.getStackTrace() + // 1: SyncLockManager.getCallerInfo() + // 2: SyncLockManager.tryLock/unlock/isLocked() + // 3+: 可能是Spring代理类或真实调用者 + + for (int i = 3; i < stackTrace.length; i++) { + StackTraceElement element = stackTrace[i]; + String className = element.getClassName(); + String methodName = element.getMethodName(); + + // 跳过 Spring 和 JDK 的内部类/代理类 + if (shouldSkipFrame(className, methodName)) { + continue; + } + + // 找到真正的业务调用者 + String simpleClassName = className.substring(className.lastIndexOf('.') + 1); + return new CallerInfo(simpleClassName, methodName, className + "." + methodName); + } + + // 兜底:使用unknown + return new CallerInfo("Unknown", "unknown", "Unknown.unknown"); + } + + /** + * 判断是否应该跳过该栈帧 + * + * @param className 类名 + * @param methodName 方法名 + * @return true-跳过,false-不跳过 + */ + private boolean shouldSkipFrame(String className, String methodName) { + // 跳过 CGLIB 动态代理类(所有包含$$的类都是CGLIB代理) + if (className.contains("$$")) { + return true; + } + + // 跳过 JDK 动态代理 + if (className.startsWith("jdk.proxy") || className.startsWith("com.sun.proxy")) { + return true; + } + + // 跳过 Spring AOP 拦截器 + if (className.contains("AsyncExecutionInterceptor") || + className.contains("TransactionInterceptor") || + className.contains("CglibAopProxy") || + className.contains("ReflectiveMethodInvocation") || + className.contains("DynamicAdvisedInterceptor")) { + return true; + } + + // 跳过线程池和任务执行器 + if (className.contains("ThreadPoolExecutor") || + className.contains("FutureTask") || + className.contains("CompletableFuture")) { + return true; + } + + // 跳过 Java 反射 + if (className.startsWith("java.lang.reflect") || + className.startsWith("jdk.internal.reflect")) { + return true; + } + + // 跳过 lambda 表达式的合成方法 + if (methodName.contains("lambda$")) { + return true; + } + + return false; + } + + /** + * 构建锁的唯一key + * + * @param callerInfo 调用者信息 + * @param params 参数列表 + * @return 锁key + */ + private String buildLockKey(CallerInfo callerInfo, Object... params) { + StringBuilder key = new StringBuilder(callerInfo.getFullMethodName()); + for (Object param : params) { + key.append(":").append(param != null ? param : "null"); + } + return key.toString(); + } + + /** + * 格式化参数用于日志输出 + * + * @param params 参数列表 + * @return 格式化字符串 + */ + private String formatParams(Object... params) { + if (params.length == 0) { + return "[]"; + } + + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(params[i]); + } + sb.append("]"); + return sb.toString(); + } + + /** + * 调用者信息 + */ + private static class CallerInfo { + private final String simpleClassName; + private final String methodName; + private final String fullMethodName; + + public CallerInfo(String simpleClassName, String methodName, String fullMethodName) { + this.simpleClassName = simpleClassName; + this.methodName = methodName; + this.fullMethodName = fullMethodName; + } + + public String getSimpleClassName() { + return simpleClassName; + } + + public String getMethodName() { + return methodName; + } + + public String getFullMethodName() { + return fullMethodName; + } + } +}