longz
2026-04-27 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66
backend/src/main/java/com/sinata/appointment/audit/OpLogAuditAspect.java
@@ -1,16 +1,30 @@
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;
/**
 * 配置类写操作审计(示例):复杂场景可在 ApplicationService 内显式记录。
 * 统一记录控制器写操作日志。
 */
@Slf4j
@Aspect
@@ -19,23 +33,139 @@
public class OpLogAuditAspect {
    private final OpLogApplicationService opLogApplicationService;
    private final ObjectMapper objectMapper;
    @AfterReturning(
            pointcut =
                    "execution(* com.sinata.appointment.service.ConfigApplicationService.save*(..))",
            returning = "ret")
    public void afterConfigSave(JoinPoint jp, Object ret) {
    @Pointcut("within(com.sinata.appointment.controller..*)")
    public void controllerWriteOperation() {}
    @AfterReturning(pointcut = "controllerWriteOperation()", returning = "ret")
    public void afterWrite(JoinPoint jp, Object ret) {
        try {
            Long op = StpUtil.isLogin() ? StpUtil.getLoginIdAsLong() : null;
            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(
                    op,
                    "CONFIG",
                    jp.getSignature().getName(),
                    jp.getArgs().length > 0 ? jp.getArgs()[0].getClass().getSimpleName() : "UNKNOWN",
                    "",
                    "{}");
                    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;
    }
}