diff --git a/src/main/java/com/label/common/context/CompanyContext.java b/src/main/java/com/label/common/context/CompanyContext.java new file mode 100644 index 0000000..1606633 --- /dev/null +++ b/src/main/java/com/label/common/context/CompanyContext.java @@ -0,0 +1,21 @@ +package com.label.common.context; + +public class CompanyContext { + private static final ThreadLocal COMPANY_ID = new ThreadLocal<>(); + + public static void set(Long companyId) { + COMPANY_ID.set(companyId); + } + + public static Long get() { + return COMPANY_ID.get(); + } + + public static void clear() { + COMPANY_ID.remove(); // Use remove() not set(null) to prevent memory leaks + } + + private CompanyContext() { // Prevent instantiation + throw new UnsupportedOperationException("Utility class"); + } +} diff --git a/src/main/java/com/label/common/exception/BusinessException.java b/src/main/java/com/label/common/exception/BusinessException.java new file mode 100644 index 0000000..1e7aede --- /dev/null +++ b/src/main/java/com/label/common/exception/BusinessException.java @@ -0,0 +1,22 @@ +package com.label.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class BusinessException extends RuntimeException { + private final String code; + private final HttpStatus httpStatus; + + public BusinessException(String code, String message) { + super(message); + this.code = code; + this.httpStatus = HttpStatus.BAD_REQUEST; + } + + public BusinessException(String code, String message, HttpStatus httpStatus) { + super(message); + this.code = code; + this.httpStatus = httpStatus; + } +} diff --git a/src/main/java/com/label/common/exception/GlobalExceptionHandler.java b/src/main/java/com/label/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..327e4e4 --- /dev/null +++ b/src/main/java/com/label/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,28 @@ +package com.label.common.exception; + +import com.label.common.result.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public ResponseEntity> handleBusinessException(BusinessException e) { + log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage()); + return ResponseEntity + .status(e.getHttpStatus()) + .body(Result.failure(e.getCode(), e.getMessage())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception e) { + log.error("系统异常", e); + return ResponseEntity + .internalServerError() + .body(Result.failure("INTERNAL_ERROR", "系统内部错误")); + } +} diff --git a/src/main/java/com/label/common/statemachine/DatasetStatus.java b/src/main/java/com/label/common/statemachine/DatasetStatus.java new file mode 100644 index 0000000..753d508 --- /dev/null +++ b/src/main/java/com/label/common/statemachine/DatasetStatus.java @@ -0,0 +1,13 @@ +package com.label.common.statemachine; + +import java.util.Map; +import java.util.Set; + +public enum DatasetStatus { + PENDING_REVIEW, APPROVED, REJECTED; + + public static final Map> TRANSITIONS = Map.of( + PENDING_REVIEW, Set.of(APPROVED, REJECTED) + // APPROVED/REJECTED: terminal states + ); +} diff --git a/src/main/java/com/label/common/statemachine/SourceStatus.java b/src/main/java/com/label/common/statemachine/SourceStatus.java new file mode 100644 index 0000000..324d673 --- /dev/null +++ b/src/main/java/com/label/common/statemachine/SourceStatus.java @@ -0,0 +1,15 @@ +package com.label.common.statemachine; + +import java.util.Map; +import java.util.Set; + +public enum SourceStatus { + PENDING, PREPROCESSING, EXTRACTING, QA_REVIEW, APPROVED; + + public static final Map> TRANSITIONS = Map.of( + PENDING, Set.of(EXTRACTING, PREPROCESSING), + PREPROCESSING, Set.of(PENDING), + EXTRACTING, Set.of(QA_REVIEW), + QA_REVIEW, Set.of(APPROVED) + ); +} diff --git a/src/main/java/com/label/common/statemachine/TaskStatus.java b/src/main/java/com/label/common/statemachine/TaskStatus.java new file mode 100644 index 0000000..ccca0aa --- /dev/null +++ b/src/main/java/com/label/common/statemachine/TaskStatus.java @@ -0,0 +1,16 @@ +package com.label.common.statemachine; + +import java.util.Map; +import java.util.Set; + +public enum TaskStatus { + UNCLAIMED, IN_PROGRESS, SUBMITTED, APPROVED, REJECTED; + + public static final Map> TRANSITIONS = Map.of( + UNCLAIMED, Set.of(IN_PROGRESS), + IN_PROGRESS, Set.of(SUBMITTED, UNCLAIMED, IN_PROGRESS), // IN_PROGRESS->IN_PROGRESS for ADMIN reassign + SUBMITTED, Set.of(APPROVED, REJECTED), + REJECTED, Set.of(IN_PROGRESS) + // APPROVED: terminal state, no outgoing transitions + ); +} diff --git a/src/main/java/com/label/common/statemachine/VideoJobStatus.java b/src/main/java/com/label/common/statemachine/VideoJobStatus.java new file mode 100644 index 0000000..0af2c9d --- /dev/null +++ b/src/main/java/com/label/common/statemachine/VideoJobStatus.java @@ -0,0 +1,20 @@ +package com.label.common.statemachine; + +import java.util.Map; +import java.util.Set; + +public enum VideoJobStatus { + PENDING, RUNNING, SUCCESS, FAILED, RETRYING; + + /** + * Automatic state machine transitions. + * Note: FAILED → PENDING is a manual ADMIN operation, handled separately in VideoProcessService.reset(). + */ + public static final Map> TRANSITIONS = Map.of( + PENDING, Set.of(RUNNING), + RUNNING, Set.of(SUCCESS, FAILED, RETRYING), + RETRYING, Set.of(RUNNING, FAILED) + // SUCCESS: terminal state + // FAILED → PENDING: manual ADMIN reset, NOT in this automatic transitions map + ); +}