package com.dhx.apicore.aop;
import com.dhx.apicommon.common.BaseResponse;
import com.dhx.apicore.common.annotation.SysLog;
import com.dhx.apicore.model.DTO.UserDTO;
import com.dhx.apicore.util.AopUtil;
import com.dhx.apicore.util.UserHolder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author <a href="https://blog.dhx.icu/"> adorabled4 </a>
 * @className SysLogAOP
 * @date : 2023/03/24/ 22:11
 **/
@Aspect
@Component
@Slf4j
public class SysLogAOP {

    // 定义切面
    @Pointcut("@annotation(com.dhx.apicore.common.annotation.SysLog)")
    public void logPointcut(){
    }

    @Around(value= "logPointcut()")
    public Object run(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        Signature signature =  joinPoint.getSignature();//方法签名
        Method method = ( (MethodSignature)signature ).getMethod();
        //这个方法才是目标对象上有注解的方法
        Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
        //获取注解
        SysLog sysLog = realMethod.getAnnotation(SysLog.class);
        Object proceed = joinPoint.proceed(args);
        if(proceed instanceof BaseResponse){
            BaseResponse result = (BaseResponse) proceed;
            LogParam params = getParams(joinPoint);
            params.setAPIInfo(method);
            if(result.getCode()==0 || result.getCode()==200){
                // 执行成功
                log.info("[成功] 用户Id:{}, 请求方式: {} , 请求路径: {} , 执行内容: {} , 参数: {}",
                        params.getUserId(),params.getHttpMethod(),params.getUrl(),sysLog.value(),params.getMethodParams());
            }else{
                // 执行失败
                log.info("[失败] 用户Id:{}, 请求方式: {} , 请求路径: {} , 执行内容: {},失败原因:{} ,参数: {}",
                        params.getUserId(),params.getHttpMethod(),params.getUrl(),sysLog.value(),result.getMessage(),params.getMethodParams());
            }
            return result;
        }else{
            return proceed;
        }
    }


    /**
     * 获取日志参数
     * @param joinPoint
     * @return
     */
    private LogParam getParams(ProceedingJoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();// 获取传入的参数
        // 封装LogParam
        LogParam logParams = new LogParam(args.length);
        Map<String, Object> params = logParams.getMethodParams();
        UserDTO user = UserHolder.getUser();
        if(user!=null){
            logParams.setUserId(user.getUserId());
        }
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        // 设置方法参数信息
        for (int i = 0; i < paramNames.length; i++) {
            if (args[i] instanceof BindingResult) {
                continue;
            }
            params.put(paramNames[i], args[i]);
        }
        return logParams;
    }
    @Data
    static class LogParam {
        /**
         * 访问用户
         */
        private Long userId;
        /**
         * 用户传入的参数
         */
        private Map<String,Object> methodParams;

        /**
         *  用户访问的接口url
         */
        private String url;

        /**
         *  请求方式
         */
        private String httpMethod;

        LogParam(int capacity){
            methodParams =new HashMap<>(capacity);
        }

        private void setAPIInfo(Method method) throws Exception {
            Annotation[] annotations = method.getAnnotations(); // 这样获取到的都是代理对象的注解
            for (Annotation annotation : annotations) {
                Annotation target = (Annotation) AopUtil.getTarget(annotation); // 获取真实注解对象
                Class<? extends Annotation> aClass = target.annotationType(); // 注解的类
                // 注解上面的注解
                // 如果注解的头上包含有@Mapping() 或者 @RequestMapping()
                if(aClass.getAnnotation(Mapping.class)!=null){
                    RequestMapping requestMapping = (RequestMapping)annotation;
                    RequestMethod[] httpMethod = requestMapping.method();
                    String[] value = requestMapping.value();
                    this.httpMethod=httpMethod[0].toString(); // 获取不到value
                    this.url=value[0];
                }else if(aClass.getAnnotation(RequestMapping.class)!=null){
                    // 注解是其他的注解(GetMapping ,PostMapping等)
                    try{
                        if(annotation instanceof GetMapping){
                            GetMapping getMapping= (GetMapping)annotation;
                            String[] value = getMapping.value();
                            this.httpMethod=HttpMethod.GET.toString(); // 强制转换获取不到value
                            this.url=value[0];
                        }else if(annotation instanceof PostMapping){
                            PostMapping getMapping= (PostMapping)annotation;
                            String[] value = getMapping.value();
                            this.httpMethod=HttpMethod.POST.toString(); // 强制转换获取不到value
                            this.url=value[0];
                        }else if(annotation instanceof DeleteMapping){
                            DeleteMapping getMapping= (DeleteMapping)annotation;
                            String[] value = getMapping.value();
                            this.httpMethod=HttpMethod.DELETE.toString(); // 强制转换获取不到value
                            this.url=value[0];
                        }else if(annotation instanceof PutMapping){
                            PutMapping getMapping= (PutMapping)annotation;
                            String[] value = getMapping.value();
                            this.httpMethod=HttpMethod.PUT.toString(); // 强制转换获取不到value
                            this.url=value[0];
                        }
                    }catch (Exception e) {
                        log.error("获取接口注解失败 ,注解为 {}",aClass);
                    }
                }
            }
        }
    }
}