前言
一个系统必备可少的就是用户的操作日志了,通过操作日志可以解决很多问题
实现
数据库表设计
/*
Navicat Premium Data Transfer
Source Server : MySQL 5.5
Source Server Type : MySQL
Source Server Version : 50554 (5.5.54)
Source Host : localhost:3306
Source Schema : tgadmin
Target Server Type : MySQL
Target Server Version : 50554 (5.5.54)
File Encoding : 65001
Date: 08/08/2023 23:34:49
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_opera_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_opera_log`;
CREATE TABLE `sys_opera_log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`module` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求模块',
`operation` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求动作',
`opera_user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人',
`ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求ip',
`location` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地理位置',
`path` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求地址',
`method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求方式',
`class_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求类方法',
`params` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求参数',
`result` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '返回结果',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '请求状态 ',
`code` int(20) NULL DEFAULT NULL COMMENT '状态码',
`msg` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '提示信息',
`cost_time` bigint(20) NULL DEFAULT NULL COMMENT '消耗时间',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 330 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
自定义注解
package com.tg.admin.common.annotation;
import java.lang.annotation.*;
/**
* @Program: tg-admin
* @ClassName SysLog
* @Author: liutao
* @Description: 系统日志
* @Create: 2023-08-04 20:36
* @Version 1.0
**/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperaLog {
String module() default " ";
String path() default " ";
String operation() default " ";
}
aop切面
package com.tg.admin.common.aop;
import com.alibaba.fastjson.JSON;
import com.tg.admin.common.Result;
import com.tg.admin.common.annotation.SysOperaLog;
import com.tg.admin.common.exception.ServiceException;
import com.tg.admin.entity.OperaLog;
import com.tg.admin.service.OperaLogService;
import com.tg.admin.utils.HttpContextUtil;
import com.tg.admin.utils.IpUtils;
import com.tg.admin.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @Program: tg-admin
* @ClassName SysLog
* @Author: liutao
* @Description: 系统日志切面
* @Create: 2023-08-04 20:39
* @Version 1.0
**/
@Slf4j
@Aspect
@Component
public class SysLogAspect {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
private OperaLog operaLog;
@Autowired
private OperaLogService operaLogService;
/***
* @MethodName: log
* @description: 定义一个切点
* @Author: LiuTao
* @UpdateTime: 2023/8/4 20:46
* @Return: void
**/
@Pointcut("@annotation(com.tg.admin.common.annotation.SysOperaLog)")
public void logPointCut() {
}
@Before("logPointCut()")
public void start() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
@Around("logPointCut()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
operaLog = new OperaLog();
operaLog.setOperaUser(JwtUtil.getCurrentUser().getUsername());
// 获取请求对象
HttpServletRequest request = HttpContextUtil.getRequest();
// 记录日志
log.info("===============系统操作日志===============");
Signature signature = pjp.getSignature();
// 请求的类
String className = pjp.getTarget().getClass().getName();
String methodName = signature.getName();
String classMethod = className + "." + methodName + "()";
Object[] args = pjp.getArgs();
String[] parameterNames = ((MethodSignature) signature).getParameterNames();
Map<Object,Object> map = new HashMap<Object,Object>();
log.info("请求方式:{}", request.getMethod());
operaLog.setMethod(request.getMethod());
log.info("请求ip:{}", request.getRemoteAddr());
operaLog.setIp(IpUtils.getIpAddr());
log.info("请求类方法:{}", classMethod);
operaLog.setClassMethod(classMethod);
if (ObjectUtils.isNotEmpty(args)){
for (int i = 0; i < args.length;i++) {
map.put(JSON.toJSON(parameterNames[i]),JSON.toJSON(args[i]));
}
if (Objects.equals(request.getMethod(), "POST")){
log.info("请求参数:{}", map.get("user"));
operaLog.setParams(JSON.toJSONString(map.get("user")));
}else {
log.info("请求参数:{}", JSON.toJSONString(map));
operaLog.setParams(JSON.toJSONString(map));
}
}
MethodSignature handlerMethod = (MethodSignature) signature;
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(SysOperaLog.class)) {
SysOperaLog sysOperaLog = method.getAnnotation(SysOperaLog.class);
operaLog.setModule(sysOperaLog.module());
operaLog.setOperation(sysOperaLog.operation());
operaLog.setPath(sysOperaLog.path());
}
return pjp.proceed();
}
@AfterThrowing(throwing = "ex",pointcut = "logPointCut()")
public void throwEx(JoinPoint joinPoint, ServiceException ex) {
log.info("异常:{}",ex);
long time = System.currentTimeMillis() - TIME_THREADLOCAL.get();
operaLog.setResult(JSON.toJSONString(ex));
operaLog.setCode(ex.getCode());
operaLog.setMsg(ex.getMessage());
operaLog.setCostTime(time);
boolean isSaved = operaLogService.save(operaLog);
if (isSaved) {
TIME_THREADLOCAL.remove();
}
}
@AfterReturning(returning = "response", pointcut = "logPointCut()")
public void end(Object response) {
long time = System.currentTimeMillis() - TIME_THREADLOCAL.get();
Result res = (Result) response;
if (res.getCode() == 200) {
operaLog.setStatus(true);
}
operaLog.setResult(JSON.toJSONString(response));
operaLog.setCode(res.getCode());
operaLog.setMsg(res.getMsg());
operaLog.setCostTime(time);
boolean isSaved = operaLogService.save(operaLog);
if (isSaved) {
TIME_THREADLOCAL.remove();
}
}
}
使用 在需要的web接口层,加上注解即可
@SysOperaLog(module = "所属模块", path = "请求地址", operation = "操作内容")
效果图