说明:
ip获取采用 ip2region
当前操作的用户: 可以用拦截器+threadlocal实现
1 pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- 获取ip --><dependency><groupId>org.lionsoul</groupId><artifactId>ip2region</artifactId><version>2.7.0</version></dependency><!-- Apache Lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!-- io常用工具类 --><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.2</version></dependency>
2 日志pojo类
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;@Data
public class TOperLog {/*** id*/private String id;/*** 操作日志内容*/private String log;/*** 状态,0:异常,1:正常*/private Integer status;/*** 请求参数json*/private String paramJson;/*** 响应结果json*/private String resultJson;/*** 错误信息(status=0时,记录错误信息)*/private String errorMsg;/*** 耗时(毫秒)*/private Long costTime;/*** 操作ip地址*/private String operIp;/*** 操作ip地址位置*/private String operIpAddress;/*** 操作人名称*/private String operUserName;/*** 操作时间*/private LocalDateTime operTime;
}
3 自定义注解,用于标注在Controller中需要记录操作日志的方法上
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {//日志内容String log();
}
4 日志记录aop,使用环绕通知,会拦截所有controller中标注有@OperLog注解的方法,会对这些方法记录日志,不管方法是成功还是失败都会记录
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.example.chao_chi_service.ip.IpAddressUtils;
import com.example.chao_chi_service.ip.IpUtils;
import com.example.chao_chi_service.log_rizhi.domain.TOperLog;
import com.example.chao_chi_service.log_rizhi.service.TOperLogService;
import com.example.chao_chi_service.log_rizhi.yonghu.IUserNameProvider;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;@Aspect
@Order
@Component
public class OperLogAspect {@Autowiredprivate TOperLogService operLogService;// 当前操作的用户,可以用拦截器+threadlocal实现@Autowiredprivate IUserNameProvider userNameProvider;/*** 环绕通知,拦截Controller中所有方法上标注有 @OperLog注解的方法,记录日志** @param joinPoint 切点对象,包含方法执行的元信息* @param operLog 操作日志注解,包含日志描述等信息* @return 方法执行结果* @throws Throwable 方法执行中抛出的异常*/@Around("@annotation(operLog) && execution(* com.example.chao_chi_service.controller..*.*(..))")public Object around(ProceedingJoinPoint joinPoint, OperLog operLog) throws Throwable {long startTime = System.currentTimeMillis();Object result = null;Throwable error = null;try {result = joinPoint.proceed();} catch (Throwable e) {error = e;throw e;} finally {this.log(joinPoint, operLog, result, error, startTime);}return result;}/*** 记录操作日志** @param joinPoint 切点对象* @param operLog 操作日志注解* @param result 方法执行结果* @param error 方法执行中抛出的异常* @param startTime 方法开始时间* @throws JsonProcessingException JSON处理异常*/private void log(ProceedingJoinPoint joinPoint, OperLog operLog, Object result, Throwable error, long startTime) throws JsonProcessingException {TOperLog operLogPO = new TOperLog();//日志idoperLogPO.setId(IdUtil.fastSimpleUUID());//日志信息,直接从注解中获取operLogPO.setLog(operLog.log());//接口参数,json格式operLogPO.setParamJson(jsonString(getParamMap(joinPoint)));//返回值,json格式operLogPO.setResultJson(jsonString(result));//状态,0:异常,1:正常,error不为空表示有异常operLogPO.setStatus(error != null ? 0 : 1);//记录异常信息if (error != null) {operLogPO.setErrorMsg(ExceptionUtil.stacktraceToString(error));}operLogPO.setCostTime(System.currentTimeMillis() - startTime);// ip的获取,采用的框架 ip2region//操作ipString operIp = IpUtils.getIpAddr();operLogPO.setOperIp(operIp);//根据ip获取ip归属地operLogPO.setOperIpAddress(IpAddressUtils.getRegion(operIp));//通过userNameProvider获取用户名,userNameProvider可以自己去实现operLogPO.setOperUserName(this.userNameProvider.getUserName());//操作时间operLogPO.setOperTime(LocalDateTime.now());//写入日志this.operLogService.save(operLogPO);}/*** 将对象转换为JSON字符串** @param obj 对象* @return JSON字符串* @throws JsonProcessingException JSON处理异常*/private String jsonString(Object obj) throws JsonProcessingException {if (obj == null) {return null;}return JSONUtil.toJsonStr(obj);}/*** 获取方法参数并转换为Map** @param joinPoint 切点对象* @return 参数Map,键为参数名,值为参数值*/private Map<String, Object> getParamMap(ProceedingJoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获取所有要打印的参数,丢到map中,key为参数名称,value为参数的值,然后会将这个map以json格式输出Map<String, Object> paramMap = new LinkedHashMap<>();String[] parameterNames = methodSignature.getParameterNames();Object[] args = joinPoint.getArgs();for (int i = 0; i < args.length; i++) {//参数名称String parameterName = parameterNames[i];//参数值Object parameterValue = args[i];//将其放入到map中,稍后会以json格式输出paramMap.put(parameterName, parameterValue);}return paramMap;}
}
5 写个controller层测试,需要记录日志的,添加注解即可
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService userService;@PostMapping("/add")@OperLog(log = "用户管理-新增用户")public Result<String> add(@Validated @RequestBody UserAddRequest req) {return ResultUtils.success(this.userService.add(req));}@PostMapping("/delete")@OperLog(log = "用户管理-删除用户")public Result<Boolean> delete(@RequestParam("userId") String userId) {//这里抛个异常,演示错误请求throw BusinessExceptionUtils.businessException("无权操作");}
}