
package com.seer.teach.common.aspect;

import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Enumeration;

/**
 * @Author: Sir-G
 * @Autograph: 安稳
 * @Description: 日志打印注解切面增强类
 * @Date: 2023-07-20 11:12:48
 */
@Aspect
@Component
@Slf4j
public class LogPrintAspect {
    /**
     * 换行符
     */
    private static final String LINE_SEPARATOR = System.lineSeparator();

    /**
     * 日志最大长度
     */
    private static final int MAX_LOG_LENGTH = 256;

    /**
     * 省略号
     */
    private static final String ELLIPSIS = "...";

    /**
     * 以自定义 @LogPrint 注解为切点
     */
    @Pointcut("@annotation(com.seer.teach.common.annotation.LogPrint) || @within(com.seer.teach.common.annotation.LogPrint)")
    public void logPrint() {
    }

    /**
     * 在切点之前织入
     *
     * @param joinPoint 切点
     * @throws Throwable
     */
    @Before("logPrint()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        try {
            // 开始打印请求日志
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

            // 增加空值检查，避免NPE
            if (attributes == null) {
                log.warn("ServletRequestAttributes is null, skipping req log");
                return;
            }

            HttpServletRequest request = attributes.getRequest();
            if (request == null) {
                log.warn("HttpServletRequest is null, skipping req log");
                return;
            }

            // 打印请求相关参数
            log.info("========================================== Start ==========================================");

            // 打印请求 url (控制长度)
            log.info("URL            : {}", truncateIfNeeded(request.getRequestURL().toString()));

            // 打印 Http method
            log.info("HTTP Method    : {}", request.getMethod());

            // 打印调用 controller 的全路径以及执行方法
            log.info("Class Method   : {}.{}",
                    joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName());

            // 打印请求的 IP
            log.info("IP             : {}", request.getRemoteAddr());

            // 打印请求头信息（控制长度）
            Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String headerName = headerNames.nextElement();
                    String headerValue = request.getHeader(headerName);
                    log.info("Request Header Args : {} : {}",
                            headerName,
                            truncateIfNeeded(headerValue));
                }
            }

            // 打印请求入参（控制长度）
            log.info("Request Args   : {}", truncateIfNeeded(getParams(joinPoint)));

        } catch (Exception e) {
            // 避免日志切面异常影响业务逻辑
            log.warn("Failed to log req info: {}", e.getMessage());
        }
    }

    /**
     * 在切点之后织入
     *
     * @throws Throwable
     */
    @After("logPrint()")
    public void doAfter() throws Throwable {
        // 接口结束后换行，方便分割查看
        log.info("=========================================== End ===========================================" + LINE_SEPARATOR);
    }

    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("logPrint()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
            // 打印出参（控制长度）
            log.info("Response Args  : {}", truncateIfNeeded(formatResponse(result)));
            return result;
        } catch (Exception e) {
            // 记录异常信息
            log.error("Exception in method: {}.{}",
                    proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                    proceedingJoinPoint.getSignature().getName(), e);
            throw e;
        } finally {
            // 执行耗时
            log.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        }
    }

    /**
     * 格式化响应结果
     * @param response 响应对象
     * @return 格式化后的字符串
     */
    private String formatResponse(Object response) {
        if (response == null) {
            return "null";
        }

        try {
            // 对于已经是字符串的响应，直接返回
            if (response instanceof String) {
                return (String) response;
            }

            // 对于文件下载等特殊类型，返回特定标识
            if (response instanceof byte[] || response instanceof HttpServletResponse) {
                return "[Binary Data or HttpServletResponse]";
            }

            // 使用JSON序列化其他对象
            return JSONObject.toJSONString(response);
        } catch (Exception e) {
            log.warn("Failed to serialize response object, using toString() method. Error: {}", e.getMessage());
            return response.toString();
        }
    }

    /**
     * 获取请求参数
     * @param joinPoint 切点
     * @return 格式化后的参数字符串
     */
    private String getParams(JoinPoint joinPoint) {
        StringBuilder params = new StringBuilder();
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                Object arg = joinPoint.getArgs()[i];
                if ((arg instanceof HttpServletResponse) || (arg instanceof HttpServletRequest)
                        || (arg instanceof MultipartFile) || (arg instanceof MultipartFile[])) {
                    continue;
                }
                try {
                    if (params.length() > 0) {
                        params.append(",");
                    }
                    String paramStr = JSONObject.toJSONString(joinPoint.getArgs()[i]);
                    params.append(paramStr);

                    // 如果已经超长，提前终止
                    if (params.length() > MAX_LOG_LENGTH) {
                        return truncateIfNeeded(params.toString());
                    }
                } catch (Exception e1) {
                    log.error(e1.getMessage());
                }
            }
        }
        return params.toString();
    }

    /**
     * 截取字符串，如果超过最大长度则添加省略号
     *
     * @param str 原始字符串
     * @return 截取后的字符串
     */
    private String truncateIfNeeded(String str) {
        if (str == null) {
            return null;
        }

        if (str.length() <= MAX_LOG_LENGTH) {
            return str;
        }

        return str.substring(0, MAX_LOG_LENGTH - ELLIPSIS.length()) + ELLIPSIS;
    }
}