自学内容网 自学内容网

springboot 通过aop+自定义注解,统一记录系统操作日志

说明:
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;
}

自定义注解,用于标注在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();
}

日志记录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("无权操作");
    }
}


原文地址:https://blog.csdn.net/qq_41712271/article/details/142745342

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!