说明:
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 {
@Autowired
private TOperLogService operLogService;
// 当前操作的用户,可以用拦截器+threadlocal实现
@Autowired
private 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();
//日志id
operLogPO.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
//操作ip
String 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 {
@Autowired
private 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("无权操作");
}
}