13.系统日志管理
目的:描述谁在什么时间干了什么事情得到什么结果,执行接口的时间过程记录清楚,如果报错则要记录报错的过程和信息
实现思路:Spring-AOP功能
技术点:AOP基于注解化、自定义注解、项目中获取当前登录的用户信息
1. 设计系统日志表
CREATE TABLE `huaweidemo`.`t_log` (
`logID` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志记录id',
`insertIP` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '记录插入者IP',
`isDelete` int(2) NULL DEFAULT 0,
`insertTime` datetime(0) NULL DEFAULT NULL COMMENT '记录插入时间',
`updateTime` datetime(0) NULL DEFAULT NULL COMMENT '记录更新时间',
`comment` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
`accountName` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作者用户名',
`path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求路径',
`getTime` datetime(0) NULL DEFAULT NULL COMMENT '请求发送时间',
`overTime` datetime(0) NULL DEFAULT NULL COMMENT '请求结束响应回复的时间',
`doTime` int(5) NULL DEFAULT NULL COMMENT '执行时间,单位毫秒',
`methodName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作方法名',
`methodType` int(2) NULL DEFAULT NULL COMMENT '方法类型: 1:get 2:post 3:put 4 :delete 5:其他',
`methodParam` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法入参',
`methodDesc` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法描述-中文',
`modelName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '功能模块名',
`sysType` int(2) NULL DEFAULT NULL COMMENT '调用系统类型:1 web 2 app端',
`resType` int(2) NULL DEFAULT NULL COMMENT '方法调用结果:1 正常 2: 异常',
`ycMsg` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '异常信息概述',
PRIMARY KEY (`logID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
根据表生成对应的controller、service等类
2. 定义一个日志注解类
/**
* 模块名称: 系统自定义注解类
* 模块类型: 注解
* 编码人:高靖博
* 创建时间:2023/3/23
* 联系电话:18587388612
*/
@Target({
ElementType.METHOD,ElementType.PARAMETER}) // 注解能放置的位置
@Retention(RetentionPolicy.RUNTIME) // 注解实例是在该注解被访问时创建且保存在运行内容中(临时对象)
public @interface MySysLog {
/**
* 模块名称
* @return
*/
String modelName() default "";
/**
* 方法类型
* @return
*/
int methodType() default 1;
/**
* 方法描述
* @return
*/
String methodDesc() default "";
/**
* 系统类型:1:web调用 2:APP调用
* @return
*/
int sysType() default 1;
}
3. 注解使用
@GetMapping("/captcha")
@ApiOperation("获取验证码")
@MySysLog(modelName="验证码",methodDesc = "获取验证码",methodType = MySysLogOpration.GET)
public MyResult getRegCode(String localID){
。。。
4. 采用AOP方法实现日志记录
引入aop依赖
<!-- aop切面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建一个日志记录aop配置类
package com.example.springbootdemo2023.core.aop;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.springbootdemo2023.bus.log.dto.Log;
import com.example.springbootdemo2023.bus.log.service.ILogService;
import com.example.springbootdemo2023.core.annotations.MySysLog;
import com.example.springbootdemo2023.core.util.MyJWTUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Date;
/**
* 模块名称: 系统日志记录AOP配置类
* 模块类型: AOP
* 编码人:高靖博
* 创建时间:2023/3/23
* 联系电话:18587388612
*/
@Aspect
@Component
public class SysLogAOPConfig {
@Resource(name = "logServiceImpl")
private ILogService logService;
private long methodDoStartTime;
// 描述一个切点 - 只要调用了@MySysLog注解的方法都要进行日志记录
@Pointcut("@annotation(com.example.springbootdemo2023.core.annotations.MySysLog)")
public void sysLogPointCut(){
}
// 通知- 前置通知: 记录方法被调用的时间,方法执行时间计时器开始
@Before("sysLogPointCut()")
public void beforeMethod(JoinPoint point){
this.methodDoStartTime = System.currentTimeMillis();
}
// 通知- 返回后通知 - 方法调用成功
@AfterReturning(value = "sysLogPointCut()", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result){
// 获取当前时间和开始时间比较 得到方法的执行时间
long doMethodTime = System.currentTimeMillis() - this.methodDoStartTime;
// 编写正常日志记录
Log myLog = new Log();
// 在spring容器中快速获取当前线程的请求对象
HttpServletRequest httpServletRequest = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);
try{
myLog.setInsertIP(getIPAddress(httpServletRequest)); // 获取客户端IP地址
myLog.setAccountName(getUserNameByToken(httpServletRequest)); // 当前请求操作的用户名(账户名)
myLog.setPath(httpServletRequest.getRequestURI()); // 请求后端地址( 如: /sys/login)
myLog.setGetTime(new Date(this.methodDoStartTime)); // 方法调用时间
myLog.setOverTime(new Date()); // 方法执行完成返回的时间
myLog.setDoTime(String.valueOf(doMethodTime)+"ms"); // 方法执行时间
// 获取aop拦截的方法 - class字节码对象
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod(); // aop拦截的方法字节码对象
String methodName = method.getName();// java方法名 - controller方法名称
myLog.setMethodParam(getParamStrByObjectArr(joinPoint.getArgs()));
myLog.setModelName(methodName);
myLog.setMethodName(methodName);
// 获取当前方法头部的@MySysLog注解
MySysLog annotation = method.getAnnotation(MySysLog.class);
if(annotation!=null){
// 方法头部编写了该注解
String modelName = annotation.modelName();
String methodDesc = annotation.methodDesc();
int methodType = annotation.methodType();
int sysType = annotation.sysType();
myLog.setModelName(modelName);
myLog.setMethodDesc(methodDesc);
myLog.setMethodType(methodType);
myLog.setSysType(sysType);
}
myLog.setResType(1);
logService.save(myLog); // 日志保存成功
}catch (Exception e){
e.printStackTrace();
}
}
// 通知 - 异常后通知 - 错误日志记录
@AfterThrowing(value = "sysLogPointCut()", throwing = "e")
public void afterException(JoinPoint joinPoint, Throwable e){
// 获取当前时间和开始时间比较 得到方法的执行时间
long doMethodTime = System.currentTimeMillis() - this.methodDoStartTime;
// 编写正常日志记录
Log myLog = new Log();
// 在spring容器中快速获取当前线程的请求对象
HttpServletRequest httpServletRequest = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);
try{
myLog.setInsertIP(getIPAddress(httpServletRequest)); // 获取客户端IP地址
myLog.setAccountName(getUserNameByToken(httpServletRequest)); // 当前请求操作的用户名(账户名)
myLog.setPath(httpServletRequest.getRequestURI()); // 请求后端地址( 如: /sys/login)
myLog.setGetTime(new Date(this.methodDoStartTime)); // 方法调用时间
myLog.setOverTime(new Date()); // 方法执行完成返回的时间
myLog.setDoTime(String.valueOf(doMethodTime)+"ms"); // 方法执行时间
// 获取aop拦截的方法 - class字节码对象
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod(); // aop拦截的方法字节码对象
String methodName = method.getName();// java方法名 - controller方法名称
myLog.setMethodParam(getParamStrByObjectArr(joinPoint.getArgs()));
myLog.setModelName(methodName);
myLog.setMethodName(methodName);
// 获取当前方法头部的@MySysLog注解
MySysLog annotation = method.getAnnotation(MySysLog.class);
if(annotation!=null){
// 方法头部编写了该注解
String modelName = annotation.modelName();
String methodDesc = annotation.methodDesc();
int methodType = annotation.methodType();
int sysType = annotation.sysType();
myLog.setModelName(modelName);
myLog.setMethodDesc(methodDesc);
myLog.setMethodType(methodType);
myLog.setSysType(sysType);
}
myLog.setResType(2);
myLog.setYcMsg(e.getMessage()+"||"+e.toString().substring(80)+"...");
logService.save(myLog); // 日志保存成功
}catch (Exception e2){
e2.printStackTrace();
}
}
/**-------------- 工具方法 ------------------------*/
/**
* 获取客户端IP地址
* @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;
}
/**
*从请求中获取请求头信息,从请求头获取token,token解析出userName签名属性
* @param request
* @return
*/
public static String getUserNameByToken(HttpServletRequest request){
String token = request.getHeader("token");
if(token == null){
return "";
}
// 获取token签名内容
try{
DecodedJWT verify = JWT.require(Algorithm.HMAC256(MyJWTUtil.getSecretKey())).build().verify(token);
String userName = verify.getClaim("userName").toString().replaceAll("\"","");
return userName;
}catch (Exception e){
return "";
}
}
public static String getParamStrByObjectArr(Object [] arr){
String str = "";
for(Object obj : arr){
str += obj.toString()+",";
}
str = str.substring(0,str.length()-1);
return str;
}
}