package com.unis.core.config.log;

import cn.hutool.core.util.IdUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unis.common.constant.LogConstant;
import com.unis.common.constant.MDCConstant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 接口日志
 *
 * @author ybw
 * @version V1.0
 * @className WebLogAspect
 * @date 2024/4/15
 **/
@Aspect
@Component
@Slf4j
public class WebLogAspect {

    private final ObjectMapper mapper;

    ThreadLocal<LogRecord> logRecordThreadLocal = new ThreadLocal<>();

    @Autowired
    public WebLogAspect(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestLog() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postLog() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getLog() {
    }

    /**
     * 请求参数
     *
     * @param joinPoint
     * @methodName: doBefore
     * @return: void
     * @author: ybw
     * @date: 2024/4/15
     **/
    @Before("requestLog() || postLog() || getLog()")
    public void doBefore(JoinPoint joinPoint) {
        //1、获取requestId
        String requestId = getRequestId();
        //2、往当前线程的MDC中存入指定的键值对
        MDC.put(MDCConstant.REQUEST_ID, requestId);
        //3、返回值设置requestId
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse httpServletResponse = sra.getResponse();
        httpServletResponse.setHeader(MDCConstant.REQUEST_ID, requestId);
        //4、打印参数日志
        //4.1 接口地址
        String name = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
        logRecordThreadLocal.set(new LogRecord(System.currentTimeMillis(), name));
        for (Object object : joinPoint.getArgs()) {
            if (object instanceof MultipartFile || object instanceof HttpServletRequest || object instanceof HttpServletResponse) {
                continue;
            }
            try {
                //4.2 打印请求参数
                String requestStr = mapper.writeValueAsString(object);
                //4.2.1 忽略文本校验
                boolean flag = LogConstant.isContainIgnoreStr(requestStr);
                if (flag) {
                    //包含忽略文本，不打印参数日志
                    requestStr = "";
                }
                //4.2.2 长度校验
                if (requestStr.length() > LogConstant.LOG_STR_MAX_LENGTH) {
                    requestStr = requestStr.substring(0, LogConstant.LOG_STR_MAX_LENGTH);
                }
                log.info("请求接口:{},请求参数 :{}", name, requestStr);
            } catch (Exception e) {
                log.info("打印请求参数错误:", e);
            }
        }
    }

    /**
     * 获取requestId
     *
     * @methodName: getRequestId
     * @return: java.lang.String
     * @author: ybw
     * @date: 2024/4/15
     **/
    private String getRequestId() {
        //1、从header中获取requestId
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = sra.getRequest();
        String requestId = httpServletRequest.getHeader(MDCConstant.REQUEST_ID);
        if (StringUtils.isNotBlank(requestId)) {
            return requestId;
        }
        return IdUtil.simpleUUID();
    }

    /**
     * 返回参数
     *
     * @param response
     * @methodName: doAfterReturning
     * @return: void
     * @author: ybw
     * @date: 2024/4/15
     **/
    @AfterReturning(returning = "response", pointcut = "requestLog() || postLog() || getLog()")
    public void doAfterReturning(Object response) {
        //1、打印返回日志
        LogRecord logRecord = logRecordThreadLocal.get();
        log.info("接口所花费时间{}毫秒,接口:{}", System.currentTimeMillis() - logRecord.getStartTime(), logRecord.getName());
        logRecordThreadLocal.remove();
        if (response != null) {
            try {
                String responseStr = mapper.writeValueAsString(response);
                if (responseStr.length() > LogConstant.LOG_STR_MAX_LENGTH) {
                    responseStr = responseStr.substring(0, LogConstant.LOG_STR_MAX_LENGTH);
                }
                log.info("返回参数:{}", responseStr);
            } catch (Exception e) {
                log.info("打印返回参数错误:", e);
            }
        }
        //2、删除当前线程MDC中指定的键值对
        MDC.remove(MDCConstant.REQUEST_ID);

    }

    @AllArgsConstructor
    @Getter
    class LogRecord {
        /**
         * 开始时间
         *
         * @author: ybwei
         * @date: 2022/3/22 9:53
         */
        private Long startTime;
        /**
         * 接口地址
         *
         * @author: ybwei
         * @date: 2022/3/22 9:55
         */
        private String name;
    }
}

