springBoot AOP记录操作日志和异常日志
1.创建日志表
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS "public"."sys_log";
CREATE TABLE "public"."sys_log" (
"id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
"thread" int4,
"ip" varchar(30) COLLATE "pg_catalog"."default",
"uri" varchar(100) COLLATE "pg_catalog"."default",
"operate_module" varchar(100) COLLATE "pg_catalog"."default",
"operate_type" varchar(50) COLLATE "pg_catalog"."default",
"operate_method" varchar(100) COLLATE "pg_catalog"."default",
"operate_desc" varchar(500) COLLATE "pg_catalog"."default",
"request_state" int4,
"request_param" varchar(500) COLLATE "pg_catalog"."default",
"response_param" varchar(1000) COLLATE "pg_catalog"."default",
"exception_name" varchar(255) COLLATE "pg_catalog"."default",
"exception_message" text COLLATE "pg_catalog"."default",
"create_time" timestamp(6),
"user_id" varchar(50) COLLATE "pg_catalog"."default",
"user_name" varchar(255) COLLATE "pg_catalog"."default",
"session_time" int4
)
;
COMMENT ON COLUMN "public"."sys_log"."id" IS 'id';
COMMENT ON COLUMN "public"."sys_log"."thread" IS '线程id';
COMMENT ON COLUMN "public"."sys_log"."ip" IS 'ip';
COMMENT ON COLUMN "public"."sys_log"."uri" IS '请求连接';
COMMENT ON COLUMN "public"."sys_log"."operate_module" IS '功能模块';
COMMENT ON COLUMN "public"."sys_log"."operate_type" IS '操作类型';
COMMENT ON COLUMN "public"."sys_log"."operate_method" IS '操作方法';
COMMENT ON COLUMN "public"."sys_log"."operate_desc" IS '操作描述';
COMMENT ON COLUMN "public"."sys_log"."request_state" IS '请求状态(1正常 2异常)';
COMMENT ON COLUMN "public"."sys_log"."request_param" IS '请求参数';
COMMENT ON COLUMN "public"."sys_log"."response_param" IS '返回参数';
COMMENT ON COLUMN "public"."sys_log"."exception_name" IS '异常名称';
COMMENT ON COLUMN "public"."sys_log"."exception_message" IS '异常信息';
COMMENT ON COLUMN "public"."sys_log"."create_time" IS '操作时间';
COMMENT ON COLUMN "public"."sys_log"."user_id" IS '用户id';
COMMENT ON COLUMN "public"."sys_log"."user_name" IS '用户名';
COMMENT ON COLUMN "public"."sys_log"."session_time" IS '请求时长(秒)';
-- ----------------------------
-- Primary Key structure for table sys_log
-- ----------------------------
ALTER TABLE "public"."sys_log" ADD CONSTRAINT "sys_log_pkey" PRIMARY KEY ("id");
2. 添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.创建操作日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {
// 操作模块
String operateModul() default "";
// 操作类型
String operateType() default "";
// 操作说明
String operateDesc() default "";
}
4.对应数据库的实体类
@Data
@TableName("sys_log")
public class SysLog implements Serializable {
private static final long serialVersionUID = 115492983341579610L;
/** id **/
@TableId(value = "id",type = IdType.ASSIGN_UUID)
private String id;
/** 线程id **/
private Integer thread;
/** ip **/
private String ip;
/** 请求连接 **/
private String uri;
/** 功能模块 **/
private String operateModule;
/** 操作类型 **/
private String operateType;
/** 操作方法 **/
private String operateMethod;
/** 操作描述 **/
private String operateDesc;
/** 请求状态(1正常 2异常) **/
private Integer requestState;
/** 请求参数 **/
private String requestParam;
/** 返回参数 **/
private String responseParam;
/** 异常名称 **/
private String exceptionName;
/** 异常信息 **/
private String exceptionMessage;
/** 操作时间 **/
private Date createTime;
/** 用户id **/
private String userId;
/** 用户名 **/
private String userName;
/** 请求时长(秒) **/
private int sessionTime;
}
5.创建切面类来记录日志
@Aspect
@Component
public class OperateLogAspect {
@Autowired
private SysLogMapper sysLogMapper;
private static ThreadLocal<Long> startTime = new ThreadLocal<Long>();
// 设置操作日志切入点 记录操作日志 在注解的位置切入代码
@Pointcut("@annotation(com.gisquest.entity.OperateLog)")
public void operatePointCut() {
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
*/
@Pointcut("execution(* com.gisquest.controller..*.*(..))")
public void operateExceptionPoinCut() {
}
/**
* 前置通知
* 获取开始的毫秒值
*/
@Before("operatePointCut()||operateExceptionPoinCut()")
public void doBefore() {
startTime.set(System.currentTimeMillis());
}
/**
* 返回通知
* 拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param keys 返回结果
*/
@AfterReturning(value = "operatePointCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
saveLogInfo(joinPoint, keys, null);
}
/**
* 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
*
* @param joinPoint 切入点
* @param e 异常信息
*/
@AfterThrowing(pointcut = "operateExceptionPoinCut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
saveLogInfo(joinPoint, null, e);
}
/**
* 保存日志信息
*
* @param joinPoint 切点
* @param keys 返回值
* @param e 异常类
*/
public void saveLogInfo(JoinPoint joinPoint, Object keys, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
SysLog sysLog = new SysLog();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
sysLog.setOperateMethod(methodName); // 请求方法
// 请求的参数
Map<String, String> rtnMap = converMap(request.getParameterMap());
// 将参数所在的数组转换成json
String params = JSON.toJSONString(rtnMap);
// 获取操作
OperateLog opLog = method.getAnnotation(OperateLog.class);
if (opLog != null) {
String operModul = opLog.operateModul();
String operType = opLog.operateType();
String operDesc = opLog.operateDesc();
sysLog.setOperateModule(operModul);
sysLog.setOperateType(operType);
sysLog.setOperateDesc(operDesc);
}
if (e != null) {
// 异常名称
sysLog.setExceptionName(e.getClass().getName());
// 异常信息
sysLog.setExceptionMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
}
//获取线程id
int threadId = (int) Thread.currentThread().getId();
sysLog.setThread(threadId);
sysLog.setRequestParam(params);
sysLog.setResponseParam(JSON.toJSONString(keys));
sysLog.setUserId(UserUtils.userId()); // 这边自己公司的工具类
sysLog.setUserName(UserUtils.nickname());// 你们可把用户信息放在Request中获取
sysLog.setIp(getIpAddressa(request));
sysLog.setUri(request.getRequestURI());
sysLog.setCreateTime(new Date());
sysLog.setRequestState(e == null ? 1 : 2);
sysLog.setSessionTime((int) ((System.currentTimeMillis() - startTime.get()) / 1000));
//插入数据到数据库
sysLogMapper.insert(sysLog);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* 转换request 请求参数
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
/**
* 转换异常信息为字符串
*
* @param exceptionName 异常名称
* @param exceptionMessage 异常信息
* @param elements 堆栈信息
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "\n");
}
String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
return message;
}
/**
* 获取IP
*
* @param request
* @return
*/
public static String getIpAddressa(HttpServletRequest request) {
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
//多次反向代理后会有多个ip值,第一个ip才是真实ip
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
int index = XFor.indexOf(",");
if (index != -1) {
return "0:0:0:0:0:0:0:1".equals(XFor.substring(0, index)) ? "127.0.0.1" : XFor.substring(0, index);
} else {
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
}
}
XFor = Xip;
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor))
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor))
XFor = request.getHeader("Proxy-Client-IP");
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor))
XFor = request.getHeader("WL-Proxy-Client-IP");
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor))
XFor = request.getHeader("HTTP_CLIENT_IP");
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor))
XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor))
XFor = request.getRemoteAddr();
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
}
}
6.在Controller中添加注解
@OperateLog(operateModul = "模块名", operateType = "查询" ,operateDesc = "描述")
@ApiOperation("通过名字获取详细信息") //swagger注解
@GetMapping("selectByName/{name}")
public Result selectByName(@PathVariable("name") String name) {
return baseInfoService.selectByName(name);
}