package com.sinata.appointment.audit;
|
|
import cn.dev33.satoken.stp.StpUtil;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.sinata.appointment.config.MiniStpUtil;
|
import com.sinata.appointment.service.OpLogApplicationService;
|
import jakarta.servlet.http.HttpServletRequest;
|
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
import java.util.LinkedHashMap;
|
import java.util.Map;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.annotation.AfterReturning;
|
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Pointcut;
|
import java.lang.reflect.Method;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.util.StringUtils;
|
|
/**
|
* 统一记录控制器写操作日志。
|
*/
|
@Slf4j
|
@Aspect
|
@Component
|
@RequiredArgsConstructor
|
public class OpLogAuditAspect {
|
|
private final OpLogApplicationService opLogApplicationService;
|
private final ObjectMapper objectMapper;
|
|
@Pointcut("within(com.sinata.appointment.controller..*)")
|
public void controllerWriteOperation() {}
|
|
@AfterReturning(pointcut = "controllerWriteOperation()", returning = "ret")
|
public void afterWrite(JoinPoint jp, Object ret) {
|
try {
|
HttpServletRequest request = currentRequest();
|
if (request == null) {
|
return;
|
}
|
if (!isWriteMethod(request.getMethod())) {
|
return;
|
}
|
String uri = request.getRequestURI();
|
String sourceType = resolveSourceType(uri);
|
if (!StringUtils.hasText(sourceType)) {
|
return;
|
}
|
Long operatorUserId = resolveOperatorUserId(sourceType);
|
opLogApplicationService.recordAsync(
|
sourceType,
|
operatorUserId,
|
resolveModule(jp, uri),
|
resolveAction(jp),
|
jp.getTarget().getClass().getSimpleName(),
|
uri,
|
buildDetailJson(request, jp));
|
} catch (Exception e) {
|
log.debug("audit skip: {}", e.getMessage());
|
}
|
}
|
|
private boolean isWriteMethod(String method) {
|
if (!StringUtils.hasText(method)) {
|
return false;
|
}
|
return "POST".equalsIgnoreCase(method)
|
|| "PUT".equalsIgnoreCase(method)
|
|| "PATCH".equalsIgnoreCase(method)
|
|| "DELETE".equalsIgnoreCase(method);
|
}
|
|
private HttpServletRequest currentRequest() {
|
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
if (attributes instanceof ServletRequestAttributes servletRequestAttributes) {
|
return servletRequestAttributes.getRequest();
|
}
|
return null;
|
}
|
|
private String resolveSourceType(String uri) {
|
if (!StringUtils.hasText(uri)) {
|
return null;
|
}
|
if (uri.startsWith("/api/mini/")) {
|
return OpLogApplicationService.SOURCE_MINI_EMPLOYEE;
|
}
|
if (uri.startsWith("/api/")) {
|
return OpLogApplicationService.SOURCE_ADMIN;
|
}
|
return null;
|
}
|
|
private Long resolveOperatorUserId(String sourceType) {
|
if (OpLogApplicationService.SOURCE_MINI_EMPLOYEE.equals(sourceType)) {
|
return MiniStpUtil.isLogin() ? MiniStpUtil.getLoginIdAsLong() : null;
|
}
|
return StpUtil.isLogin() ? StpUtil.getLoginIdAsLong() : null;
|
}
|
|
private String resolveModule(JoinPoint jp, String uri) {
|
Tag tag = jp.getTarget().getClass().getAnnotation(Tag.class);
|
if (tag != null && StringUtils.hasText(tag.name())) {
|
return tag.name().trim();
|
}
|
if (!StringUtils.hasText(uri)) {
|
return "UNKNOWN";
|
}
|
String trimmed = uri.startsWith("/api/") ? uri.substring(5) : uri;
|
String[] segments = trimmed.split("/");
|
if (segments.length >= 2 && "mini".equalsIgnoreCase(segments[0])) {
|
return segments[1].toUpperCase();
|
}
|
return segments.length > 0 ? segments[0].toUpperCase() : "UNKNOWN";
|
}
|
|
private String resolveAction(JoinPoint jp) {
|
if (jp.getSignature() instanceof MethodSignature methodSignature) {
|
Method method = methodSignature.getMethod();
|
Operation operation = method.getAnnotation(Operation.class);
|
if (operation != null && StringUtils.hasText(operation.summary())) {
|
return operation.summary().trim();
|
}
|
}
|
return jp.getSignature().getName();
|
}
|
|
private String buildDetailJson(HttpServletRequest request, JoinPoint jp) {
|
Map<String, Object> payload = new LinkedHashMap<>();
|
payload.put("method", request.getMethod());
|
payload.put("uri", request.getRequestURI());
|
payload.put("handler", jp.getSignature().toShortString());
|
payload.put("args", summarizeArgs(jp.getArgs()));
|
try {
|
return objectMapper.writeValueAsString(payload);
|
} catch (Exception ex) {
|
return "{\"uri\":\"" + request.getRequestURI() + "\"}";
|
}
|
}
|
|
private Object[] summarizeArgs(Object[] args) {
|
if (args == null || args.length == 0) {
|
return new Object[0];
|
}
|
Object[] summarized = new Object[args.length];
|
for (int i = 0; i < args.length; i++) {
|
Object arg = args[i];
|
if (arg == null) {
|
summarized[i] = null;
|
continue;
|
}
|
if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) {
|
String value = String.valueOf(arg);
|
summarized[i] =
|
value.length() > 60
|
? value.substring(0, 60) + "..."
|
: value;
|
continue;
|
}
|
summarized[i] = arg.getClass().getSimpleName();
|
}
|
return summarized;
|
}
|
}
|