refactor: flatten infrastructure packages
This commit is contained in:
76
src/main/java/com/label/aspect/AuditAspect.java
Normal file
76
src/main/java/com/label/aspect/AuditAspect.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package com.label.aspect;
|
||||
|
||||
import com.label.annotation.OperationLog;
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user