基于注解的日志持久化
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