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 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; } }