feat(common): 添加 @OperationLog 注解和 AuditAspect (T016/T017)
This commit is contained in:
75
src/main/java/com/label/common/aop/AuditAspect.java
Normal file
75
src/main/java/com/label/common/aop/AuditAspect.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package com.label.common.aop;
|
||||
|
||||
import com.label.common.context.CompanyContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* AOP aspect for audit logging.
|
||||
*
|
||||
* KEY DESIGN DECISIONS:
|
||||
* 1. Uses JdbcTemplate directly (not MyBatis Mapper) to bypass TenantLineInnerInterceptor
|
||||
* — operation logs need to capture company_id explicitly, not via thread-local injection
|
||||
* 2. Written in finally block — audit log is written regardless of business method success/failure
|
||||
* 3. Audit failures are logged as ERROR but NEVER rethrown — business transactions must not be
|
||||
* affected by audit failures
|
||||
* 4. Captures result of business method to log SUCCESS or FAILURE
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuditAspect {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Around("@annotation(operationLog)")
|
||||
public Object audit(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
|
||||
Long companyId = CompanyContext.get();
|
||||
// operator_id can be obtained from SecurityContext or ThreadLocal in the future
|
||||
// For now, use null as a safe default when not available
|
||||
Long operatorId = null;
|
||||
|
||||
String result = "SUCCESS";
|
||||
String errorMessage = null;
|
||||
Object returnValue = null;
|
||||
|
||||
try {
|
||||
returnValue = joinPoint.proceed();
|
||||
} catch (Throwable e) {
|
||||
result = "FAILURE";
|
||||
errorMessage = e.getMessage();
|
||||
throw e; // Always rethrow business exceptions
|
||||
} finally {
|
||||
// Write audit log in finally block — runs regardless of success or failure
|
||||
// CRITICAL: Never throw from here — would swallow the original exception
|
||||
try {
|
||||
writeAuditLog(companyId, operatorId, operationLog.type(),
|
||||
operationLog.targetType(), result, errorMessage);
|
||||
} catch (Exception auditEx) {
|
||||
// Audit failure must NOT affect business transaction
|
||||
log.error("审计日志写入失败: type={}, error={}",
|
||||
operationLog.type(), auditEx.getMessage(), auditEx);
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private void writeAuditLog(Long companyId, Long operatorId, String operationType,
|
||||
String targetType, String result, String errorMessage) {
|
||||
String sql = """
|
||||
INSERT INTO sys_operation_log
|
||||
(company_id, operator_id, operation_type, target_type, result, error_message, operated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW())
|
||||
""";
|
||||
jdbcTemplate.update(sql, companyId, operatorId, operationType,
|
||||
targetType.isEmpty() ? null : targetType,
|
||||
result, errorMessage);
|
||||
}
|
||||
}
|
||||
18
src/main/java/com/label/common/aop/OperationLog.java
Normal file
18
src/main/java/com/label/common/aop/OperationLog.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.label.common.aop;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Marks a method for audit logging.
|
||||
* The AuditAspect intercepts this annotation and writes to sys_operation_log.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface OperationLog {
|
||||
/** Operation type, e.g., "EXTRACTION_APPROVE", "USER_LOGIN", "TASK_CLAIM" */
|
||||
String type();
|
||||
|
||||
/** Target entity type, e.g., "annotation_task", "sys_user" */
|
||||
String targetType() default "";
|
||||
}
|
||||
Reference in New Issue
Block a user