package com.app.aspect.platform;

import com.app.util.HttpUtil;
import com.app.util.JacksonUtil;
import com.app.util.UserAgentUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.bouncycastle.asn1.ocsp.ResponseData;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Enumeration;

/**
 * 功能描述：
 * todo https://juejin.im/post/5d2001bb6fb9a07edf276593
 * @Auther: 梁展鹏
 * @Date: 2018/7/24 11:38
 */
@Slf4j
@Aspect
@Component
public class ControllerAspect {

    @Autowired
    private HttpUtil httpUtil;

    /* 保证每个线程都有一个单独的实例 */
    private ThreadLocal<Long> threadLocal = new ThreadLocal();

    /**
     * 功能描述：切点定义
     * 切面 controller 对所有的方法起作用
     */
    @Pointcut("execution(* com.app.controller..*(..))")
    public void executeController() {}

    /**
     * 功能描述：方法执行前调用
     *
     * @param joinPoint
     */
    @Before("executeController()")
    public void before(JoinPoint joinPoint) {
        /* 设置当前的时间 */
        threadLocal.set(System.currentTimeMillis());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        /* 记录请求的内容 */
        log.info("请求开始时间：{}", LocalDateTime.now());
        log.info("请求URL：【{}】 {}", request.getMethod(), request.getRequestURL().toString());
        log.info("请求方法：{}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        log.info("请求ip：{}", request.getRemoteAddr());
        log.info("请求参数：{}", Arrays.toString(joinPoint.getArgs()));
        log.info("Cookies：{}", request.getCookies());

        /* 系统信息 */
        log.info("操作系统：{}, 浏览器：{}, 浏览器版本：{}", UserAgentUtil.getOperatingSystem(request), UserAgentUtil.getBrowserName(request), UserAgentUtil.getBrowserVersion(request));

        /* 所有的请求的入参 */
        Enumeration<String> enums = request.getParameterNames();
        while (enums.hasMoreElements()) {
            String paraName = enums.nextElement();
            log.info(paraName + ":" + request.getParameter(paraName));
        }

        /* 记录操作日志 */
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
    }

    /**
     * 功能描述：执行后调用
     *
     * @param joinPoint
     */
    @After("executeController()")
    public void after(JoinPoint joinPoint) {
        log.info("doAfter(): [{}]", joinPoint.toString());
    }

    /**
     * 功能描述：执行返回值调用
     *
     * @param joinPoint
     */
    @AfterReturning(pointcut = "executeController()")
    public void afterReturning(JoinPoint joinPoint) {
        log.info("耗时: [{}]'ms", ((System.currentTimeMillis() - threadLocal.get())));
    }

    /**
     * 异常通知：
     * 1. 在目标方法非正常结束，发生异常或者抛出异常时执行
     * 1. 在异常通知中设置异常信息，并将其保存
     *
     * @param throwable
     */
    @AfterThrowing(value = "executeController()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {
        log.error("发生异常时间：[{}]", LocalDateTime.now());
        log.error("抛出异常：[{}]",throwable.getMessage());
    }

    @Around("@annotation(org.springframework.web.bind.annotation.RestController)")
    public Object restController(ProceedingJoinPoint point) throws Throwable {
        //记录请求开始执行时间：
        long beginTime = System.currentTimeMillis();
        //获取请求信息
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();

        //获取代理地址、请求地址、请求类名、方法名
        String remoteAddress = httpUtil.getIpAddr(request);
        String requestURI = request.getRequestURI();
        String methodName = point.getSignature().getName();
        String clazzName = point.getTarget().getClass().getSimpleName();

        //获取请求参数：
        MethodSignature ms = (MethodSignature) point.getSignature();
        //获取请求参数类型
        String[] parameterNames = ms.getParameterNames();
        //获取请求参数值
        Object[] parameterValues = point.getArgs();
        StringBuilder sb = new StringBuilder();
        //组合请求参数，进行日志打印
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                if (parameterNames[i].equals("bindingResult")) {
                    break;
                }
                if ((parameterValues[i] instanceof HttpServletRequest) || (parameterValues[i] instanceof HttpServletResponse)) {
                    sb.append("[")
                            .append(parameterNames[i])
                            .append("=")
                            .append(parameterValues[i])
                            .append("]");
                } else {
                    sb.append("[")
                            .append(parameterNames[i]).append("=")
                            .append(JacksonUtil.toJSON(parameterValues[i]))
                            .append("]");
                }
            }
        }

        Object result = null;
        try {
            result = point.proceed();
        } catch (Throwable throwable) {
            //请求操纵失败
            //记录错误日志
            log.error("(ง•̀_•́)ง (っ•̀ω•́)っ          切面处理请求错误！ IP信息(ง•̀_•́)ง->： 【{}}】 " +
                            "URI信息(ง•̀_•́)ง->：【{}】 请求映射控制类(ง•̀_•́)ง->：【{}】 " +
                            "请求方法(ง•̀_•́)ง->：【{}】 请求参数列表(ง•̀_•́)ง->：【{}】", remoteAddress, requestURI, clazzName, methodName,
                    sb.toString());
            throw throwable;
        }
        //请求操作成功
        String resultJosnString = "";
        if (result != null) {
            if (result instanceof ResponseData) {
                resultJosnString = JacksonUtil.toJSON(result);
            } else {
                resultJosnString = String.valueOf(result);
            }
        }
        //记录请求完成执行时间：
        long endTime = System.currentTimeMillis();
        long usedTime = endTime - beginTime;
        //记录日志
        log.info("请求操作成功！ 请求耗时：【{}】 " +
                        "IP信息(◍'౪`◍)ﾉﾞ->： 【{}}】  URI信息(◍'౪`◍)ﾉﾞ->：【{}】 " +
                        "请求映射控制类(◍'౪`◍)ﾉﾞ->：【{}】 请求方法(◍'౪`◍)ﾉﾞ->：【{}】 " +
                        "请求参数列表(◍'౪`◍)ﾉﾞ->：【{}】 返回值(◍'౪`◍)ﾉﾞ->：【{}】", usedTime, remoteAddress, requestURI, clazzName,
                methodName, sb.toString(), resultJosnString);

        return result;
    }

}
