Title

基于注解的日志持久化

1、注解 Annotation

介绍:

Java注解是附加在代码中的一些元信息,用于编译和运行时进行解析和使用,起到说明、配置的功能。注解不会影响代码的实际逻辑,仅仅起到辅助性的作用。包含在java.lang.annotation包中。注解的定义类似于接口的定义,使用@interface来定义,定义一个方法即为注解类型定义了一个元素,方法的声明不允许有参数或throw语句,返回值类型被限定为原始数据类型、字符串String、Class、enums、注解类型,或前面这些的数组,方法可以有默认值。注解并不直接影响代码的语义,但是他可以被看做是程序的工具或者类库。它会反过来对正在运行的程序语义有所影响。注解可以从源文件、class文件或者在运行时通过反射机制多种方式被读取。

java元注解

元注解是指注解的注解。包括 @Retention @Target @Document @Inherited四种。(java.lang.annotation中提供,为注释类型)。

注解 说明
@Target 定义注解的作用目标
@Retention 定义注解的保留策略。RetentionPolicy.SOURCE:注解仅存在于源码中,在class字节码文件中不包含;RetentionPolicy.CLASS:默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得;RetentionPolicy.RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Document 说明该注解将被包含在javadoc中
@Inherited 说明子类可以继承父类中的该注解

Target类型说明

Target类型 说明
ElementType.TYPE 接口、类、枚举、注解
ElementType.FIELD 字段、枚举的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE

2、通过AOP机制实现日志持久化注解

编写一个注解接口用于实现日志的持久化

package com.zl.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 日志持久化注解
 *
 * @author z
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SaveLog {
	String value() default "";
}

编写一个AOP类用于该接口实现日志拦截并写入数据库中

package com.zl.config.aspect;

import com.google.gson.Gson;
import com.zl.config.annotation.SaveLog;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import com.zl.utils.IPUtil;
import com.zl.entity.LogEntity;
import com.zl.entity.UserEntity;
import com.zl.service.LogService;
import org.apache.shiro.SecurityUtils;
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.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;


/**
 * 保存日志
 *
 * @author z
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {
	private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	/**
	 * 日志service
	 */
	@Resource
	private LogService logService;
	
	@Pointcut("@annotation(com.config.annotation.SaveLog)")
	public void pointCut() { }

	@Around("pointCut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		long beginTime = System.currentTimeMillis();
		//执行方法
		Object result = point.proceed();

		long nowTime = LocalDateTime.now().format(dateTimeFormatter);
                //执行时长(毫秒)
		long time=nowTime-beginTime;
		//保存日志
		saveLogs(point, time);

		return result;
	}
	// 保存日志
	private void saveLogs(ProceedingJoinPoint joinPoint, long time) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();

		LogEntity logEntity = new LogEntity();
		SaveLog saveLog = method.getAnnotation(SaveLog.class);
		if(saveLog != null){
			//注解的描述
			logEntity.setOperation(saveLog.value());
		}
		//请求的方法名
		String className = joinPoint.getTarget().getClass().getName();
		String methodName = signature.getName();
		logEntity.setMethod(className + "." + methodName + "()");

		//请求的参数
		Object[] args = joinPoint.getArgs();
		try{
			String params = new Gson().toJson(args);
			logEntity.setParams(params);
		}catch (Exception e){
			log.info("{}",e.getStackTrace());
		}

		//获取request
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		//设置IP地址,IpUtil是获取ip的工具包
		logEntity.setIp(IpUtil.getIpAddr(request));

		//用户名
		// String username = ((UserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername(); // shiro
		SecurityContextImpl securityContextImpl = (SecurityContextImpl) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");  // spring security
 		String username=securityContextImpl.getAuthentication().getName();
		logEntity.setUsername(username);
		// 设置执行时间
		logEntity.setTime(time);
		// 设置创建时间
		logEntity.setCreateDate(new Date());
		//保存系统日志
		logService.save(logEntity);
	}
}

数据库表:

CREATE TABLE `t_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `operation` varchar(50) DEFAULT NULL COMMENT '用户操作',
  `method` varchar(200) DEFAULT NULL COMMENT '请求方法',
  `params` varchar(5000) DEFAULT NULL COMMENT '请求参数',
  `time` bigint(20) NOT NULL COMMENT '执行时长(毫秒)',
  `ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8mb4 COMMENT='日志';

3、使用

    /**
     * 保存用户
     */
    @SaveLog("保存用户") // 使用注解添加日志
    @PostMapping("/saveUser")
    public Result saveUser(@RequestBody UserEntity user) {
        userService.saveUser(user);
        return Result.ok();
    }

4、非注解

eg:

package com.zl.constants;

import org.springframework.stereotype.Component;

import java.util.regex.Pattern;

@Component
public class CheckTemplate {

    /**
     * 匹配查询规则
     */
    public static final Pattern SELECT_PATTEN = Pattern.compile("^(get|query|select)[A-Za-z]*$");
    /**
     * 匹配删除规则
     */
    public static final Pattern DELETE_PATTEN = Pattern.compile("^(remove|delete)[A-Za-z]*$");
    /**
     * 匹配添加规则
     */
    public static final Pattern SAVE_PATTEN = Pattern.compile("^(add|save|insert)[A-Za-z]*$");
    /**
     * 匹配修改规则
     */
    public static final Pattern UPDATE_PATTEN = Pattern.compile("^[A-Za-z]*(u|U)pdate[A-Za-z]*$");
    /**
     * 匹配下载规则
     */
    public static final Pattern DOWN_PATTEN = Pattern.compile("^[A-Za-z]*(e|E)xport[A-Za-z]*$");
}
package com.zl.config;

import com.zl.constants.CheckTemplate;
import com.zl.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;


/**
 * 统一日志处理切面
 */
@Aspect
@Component
@Order(1)
@Slf4j
public class WebLogAspect {

    //定义切点表达式,指定通知功能被应用的范围
    @Pointcut("execution(public * com.zl.web..*.*(..))")
    public void webLog() {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //获取当前登录用户名
        String userName = "测试";
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        // 获取注解@Api
        Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
        // 获取注解@ApiOperation
        ApiOperation annotation = AnnotationUtils.findAnnotation(method, ApiOperation.class);

        StringBuilder content = new StringBuilder();

        try {
            Object result = joinPoint.proceed();
            content.append("用户名:").append(userName);
            content.append("\n模块入口:").append(api.tags()[0] + "-" + annotation.value());
            content.append("\n请求方式:").append(request.getMethod());
            content.append("\n请求地址:").append(request.getRequestURL().toString());
            content.append("\nip地址:").append(request.getRemoteAddr().toString());
            if (CheckTemplate.SELECT_PATTEN.matcher(signature.getName()).matches()) {
                content.append("\n操作类型:").append("查询");
            } else if (CheckTemplate.DELETE_PATTEN.matcher(signature.getName()).matches()) {
                content.append("\n操作类型:").append("删除");
            } else if (CheckTemplate.UPDATE_PATTEN.matcher(signature.getName()).matches()) {
                content.append("\n操作类型:").append("更新");
            } else if (CheckTemplate.SAVE_PATTEN.matcher(signature.getName()).matches()) {
                content.append("\n操作类型:").append("保存");
            } else if (CheckTemplate.DOWN_PATTEN.matcher(signature.getName()).matches()) {
                content.append("\n操作类型:").append("下载");
            } else {
                content.append("\n操作类型:").append("其他");
            }
            content.append("\n返回结果:").append(result);
            return result.ok();
        } catch (Exception e) {
            content.append("用户名:").append(userName);
            content.append("\n模块入口:").append(api.tags()[0] + "-" + annotation.value());
            content.append("\n请求方式:").append(request.getMethod());
            content.append("\nip地址:").append(request.getRequestURL().toString());
            content.append("\n异常信息:").append(e.toString());
            log.error("{}", e);
            return Result.failed("系统异常");
        } finally {
            // 保存日志
            ...
        }
    }
    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
     *
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
     *
     * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
     * 192.168.1.100
     *
     * 用户真实IP为: 192.168.1.110
     *
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request)
    {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

参考链接:
https://blog.csdn.net/doc_sgl/article/details/50367083

posted @ 2022-02-21 18:44  快乐小洋人  阅读(91)  评论(0编辑  收藏  举报