/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.sys.aspect;

import cn.hutool.core.date.SystemClock;
import com.yami.shop.common.util.IpHelper;
import com.yami.shop.common.util.Json;
import com.yami.shop.security.admin.util.SecurityUtils;
import com.yami.shop.sys.model.SysLog;
import com.yami.shop.sys.service.SysLogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * SysLogAspect类，用于实现系统日志记录的切面功能。
 * 通过AOP（面向切面编程）的方式，拦截被特定注解标记的方法，在方法执行前后进行相关操作，
 * 主要用于收集系统操作的相关信息并保存为系统日志，方便后续的审计、排查问题等操作。
 *
 * @author lgh
 */
@Aspect
@Component
public class SysLogAspect {

    // 自动注入SysLogService，用于将收集到的日志信息保存到数据库等存储介质中
    @Autowired
    private SysLogService sysLogService;

    // 使用SLF4J的Logger来记录日志信息，方便在出现问题或者调试时输出相关的提示信息
    private static Logger logger = LoggerFactory.getLogger(SysLogAspect.class);

    /**
     * Around注解表示这是一个环绕通知方法，它会在目标方法执行前后进行增强处理。
     * 这里拦截了被@SysLog注解标记的方法，在方法执行前记录开始时间，执行后计算执行时长，
     * 并收集如操作描述、请求方法名、参数、IP地址、用户名等信息，封装成SysLog对象后保存到系统日志中。
     *
     * @param joinPoint 连接点对象，通过它可以获取目标方法的相关信息，如方法名、参数等
     * @param sysLog    被拦截方法上标注的@SysLog注解对象，用于获取操作描述信息
     * @return 目标方法执行的结果，保证原方法的执行逻辑不受影响并能将结果正确返回给调用者
     * @throws Throwable 如果目标方法执行过程中抛出异常，会在这里继续向上抛出，保证异常处理机制正常工作
     */
    @Around("@annotation(sysLog)")
    public Object around(ProceedingJoinPoint joinPoint, com.yami.shop.common.annotation.SysLog sysLog) throws Throwable {
        // 获取方法执行前的时间戳（使用SystemClock.now()获取更精准的时间，可能比System.currentTimeMillis()性能更好）
        long beginTime = SystemClock.now();

        // 执行被拦截的目标方法，获取其返回结果。如果目标方法有异常抛出，会在这里直接抛出，后续代码不会执行
        Object result = joinPoint.proceed();

        // 计算方法执行所花费的时间（单位：毫秒），通过当前时间减去方法开始执行的时间得到
        long time = SystemClock.now() - beginTime;

        // 创建一个SysLog实体对象，用于封装要保存的系统日志信息
        SysLog sysLogEntity = new SysLog();

        // 如果被拦截的方法上标注的@SysLog注解不为空，则将注解中的描述信息设置为系统日志的操作描述
        if (sysLog!= null) {
            // 注解上的描述，通常是对该操作的简要说明，例如“用户登录操作”等
            sysLogEntity.setOperation(sysLog.value());
        }

        // 获取请求的方法名，通过连接点对象获取目标类的全限定名以及方法名，拼接成完整的方法调用表示形式
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        sysLogEntity.setMethod(className + "." + methodName + "()");

        // 获取请求的参数，通过连接点对象获取方法的参数数组，这里将参数转换为JSON字符串进行存储（方便查看参数详情），
        // 目前只取了参数数组中的第一个元素进行转换，如果参数有多个可能需要更完善的处理逻辑
        Object[] args = joinPoint.getArgs();
        String params = Json.toJsonString(args[0]);
        sysLogEntity.setParams(params);

        // 设置IP地址，通过IpHelper工具类获取客户端的IP地址信息，并设置到系统日志实体中
        sysLogEntity.setIp(IpHelper.getIpAddr());

        // 获取用户名，通过SecurityUtils工具类获取当前登录系统的用户信息，再获取用户名，设置到系统日志实体中
        String username = SecurityUtils.getSysUser().getUsername();
        sysLogEntity.setUsername(username);

        // 设置方法执行时长（单位：毫秒）到系统日志实体中
        sysLogEntity.setTime(time);

        // 设置系统日志的创建日期，使用当前时间作为创建时间
        sysLogEntity.setCreateDate(new Date());

        // 调用SysLogService的save方法，将封装好的系统日志实体保存到数据库或者其他存储介质中，实现系统日志记录功能
        sysLogService.save(sysLogEntity);

        // 返回目标方法执行的结果，保证原方法调用链的正常返回逻辑
        return result;
    }
}