package com.sunxung.factoring.component.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sunxung.factoring.component.annotation.OperationLog;
import com.sunxung.factoring.component.exception.ValidatorException;
import com.sunxung.factoring.component.util.CodeUtil;
import com.sunxung.factoring.component.util.IpUtil;
import com.sunxung.factoring.component.util.ThreadLocalUtil;
import com.sunxung.factoring.entity.sys.LoginLog;
import com.sunxung.factoring.entity.sys.SysOperationLog;
import com.sunxung.factoring.entity.sys.User;
import com.sunxung.factoring.service.sys.ISysOperationLogService;
import com.sunxung.factoring.service.sys.LoginLogService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;


/**
 * @author liutao
 * @date 2023-08-08
 * 客户操作日志
 */
@Aspect
@Component
public class OperationLogAspect {

    @Autowired
    private ISysOperationLogService sysOperationLogService;

    @Autowired
    private LoginLogService loginLogService;

    private static Logger log = LogManager.getLogger(OperationLogAspect.class);

    /**
     * 设置操作日志切入点，在注解的位置切入代码
     */
    @Pointcut("execution(public * com.sunxung.factoring.web..*Controller.*(..))")
    public void operationLogPointCut() {
    }

    /**
     * 定义环绕通知
     * 1. 环绕通知需要携带ProceedingJoinPoint类型的参数
     * 2. 环绕通知类似于动态代理的全过程ProceedingJoinPoint类型的参数可以决定是否执行目标方法
     * 3. 且环绕通知必须有返回值，返回值即目标方法的返回值
     */
    @Around("operationLogPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {

        Object result = null;
        //获取系统当前时间毫秒值
        long beginTime = System.currentTimeMillis();
        try {
            //执行目标方法
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            StackTraceElement[] stackElements = e.getStackTrace();
            if (stackElements != null) {
                StringBuffer error = new StringBuffer();
                for (int i = 0; i < stackElements.length; i++) {
                    String className = stackElements[i].getClassName();
                    String fileName = "(" + stackElements[i].getFileName();
                    String lineNumber = ":" + stackElements[i].getLineNumber() + ")";
                    String methodName = "." + stackElements[i].getMethodName();
                    error.append(className);
                    error.append(methodName);
                    error.append(fileName);
                    error.append(lineNumber);
                    error.append("\n");
                }
                log.error(error.toString());
            }
            throw new ValidatorException(CodeUtil.FAIL, e.getMessage());
        } finally {
            try {
                // 从切面织入点处通过反射机制获取织入点处的方法
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                // 获取切入点所在的方法
                Method method = signature.getMethod();
                // 获取操作
                OperationLog operationLog = method.getAnnotation(OperationLog.class);
                if (operationLog != null) {
                    SysOperationLog sysOperationLog = new SysOperationLog();
                    sysOperationLog.setOperationModule(operationLog.operationModule());
                    sysOperationLog.setOperationType(operationLog.operationType().getOperationType());
                    sysOperationLog.setDescription(operationLog.description());
                    //获取系统当前时间毫秒值
                    long endTime = System.currentTimeMillis();

                    // 获取RequestAttributes
                    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

                    // 从获取RequestAttributes中获取HttpServletRequest的信息
                    HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
                    //获取ip
                    String clientIP = IpUtil.getIpAddress(request);
                    sysOperationLog.setClientIp(clientIP);
                    //获取目标方法执行时间
                    long usageTime = endTime - beginTime;
                    sysOperationLog.setUsageTime(usageTime);
                    // 获取请求的类名
                    String className = joinPoint.getTarget().getClass().getName();
                    // 获取请求的方法名
                    String methodName = method.getName();
                    methodName = className + "." + methodName;
                    sysOperationLog.setMethodName(methodName);
                    // 请求的参数
                    Map<String, String> parameterMap = ServletUtil.getParamMap(request);
                    // 将参数所在的数组转换成json
                    String requestParams = new ObjectMapper().writeValueAsString(parameterMap);
                    // 请求参数
                    sysOperationLog.setRequestParams(requestParams);
                    // 返回结果
                    String resultStr = new ObjectMapper().writeValueAsString(result);
                    sysOperationLog.setResult(resultStr);
                    // 请求URL
                    sysOperationLog.setUrl(request.getRequestURL().toString());
                    User user = ThreadLocalUtil.getUser();
                    sysOperationLog.setUserCreate(user.getLoginName());
                    sysOperationLog.setUserModified(user.getLoginName());
                    //登录日志id
                    LoginLog loginLog = loginLogService.findLastLogInByUserAndIp(user.getLoginName(), clientIP);
                    if(loginLog!=null){
                        sysOperationLog.setSysLoginLogId(loginLog.getId());
                    }
                    sysOperationLogService.save(sysOperationLog);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}
