Files
crawler-plugin/backend-java/src/main/java/com/nanri/aiimage/config/TaskOperationLockConfig.java
T
2026-06-05 21:25:03 +08:00

107 lines
4.5 KiB
Java

package com.nanri.aiimage.config;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.modules.task.service.TaskDistributedLockService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Configuration
@RequiredArgsConstructor
public class TaskOperationLockConfig implements WebMvcConfigurer {
private static final String LOCK_ATTRIBUTE = TaskOperationLockConfig.class.getName() + ".LOCK";
private final TaskDistributedLockService taskDistributedLockService;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TaskOperationLockInterceptor(taskDistributedLockService))
.addPathPatterns("/api/**");
}
public static boolean releaseRequestLock(HttpServletRequest request) {
if (request == null) {
return false;
}
Object lockHandle = request.getAttribute(LOCK_ATTRIBUTE);
if (lockHandle instanceof TaskDistributedLockService.LockHandle handle) {
request.removeAttribute(LOCK_ATTRIBUTE);
handle.close();
return true;
}
return false;
}
@Slf4j
private static final class TaskOperationLockInterceptor implements HandlerInterceptor {
private static final Pattern TASK_PATH = Pattern.compile(".*/api/([^/]+)/tasks/(\\d+)(?:/.*)?$");
private static final Pattern TASK_RESULT_PATH = Pattern.compile(".*/api/([^/]+)/tasks/(\\d+)/result/?$");
private static final Set<String> MUTATING_METHODS = Set.of("POST", "PUT", "PATCH", "DELETE");
private static final long RESULT_SUBMIT_WAIT_MILLIS = 30 * 1000L;
private static final long DELETE_WAIT_MILLIS = 60 * 1000L;
private final TaskDistributedLockService taskDistributedLockService;
private TaskOperationLockInterceptor(TaskDistributedLockService taskDistributedLockService) {
this.taskDistributedLockService = taskDistributedLockService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!MUTATING_METHODS.contains(request.getMethod())) {
return true;
}
String uri = request.getRequestURI();
if ("POST".equals(request.getMethod()) && TASK_RESULT_PATH.matcher(uri == null ? "" : uri).matches()) {
return true;
}
Matcher matcher = TASK_PATH.matcher(uri == null ? "" : uri);
if (!matcher.matches()) {
return true;
}
String moduleType = matcher.group(1).trim().toUpperCase(Locale.ROOT).replace('-', '_');
Long taskId = Long.valueOf(matcher.group(2));
long waitMillis = resolveWaitMillis(request.getMethod(), uri);
TaskDistributedLockService.LockHandle lockHandle =
taskDistributedLockService.acquire(moduleType, taskId, waitMillis);
if (lockHandle == null) {
log.info("[task-operation-lock] rejected busy task operation method={} uri={} moduleType={} taskId={} waitMillis={}",
request.getMethod(), uri, moduleType, taskId, waitMillis);
throw new BusinessException(40902, "TASK_LOCK_BUSY");
}
request.setAttribute(LOCK_ATTRIBUTE, lockHandle);
return true;
}
private long resolveWaitMillis(String method, String uri) {
if ("POST".equals(method) && TASK_RESULT_PATH.matcher(uri == null ? "" : uri).matches()) {
return RESULT_SUBMIT_WAIT_MILLIS;
}
if ("DELETE".equals(method)) {
return DELETE_WAIT_MILLIS;
}
return TaskDistributedLockService.DEFAULT_WAIT_MILLIS;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
releaseRequestLock(request);
}
}
}