SpringBoot3 日志功能切面注解功能实现(原创)

1.添加Maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>

2.定义切面接口

  package vip.appdesign.suncms.common.annotation;

  import java.lang.annotation.*;

  /**
   * 系统日志注解
   * @author sun
   * @date 2025/7/16
   */
   @Target(ElementType.METHOD)
   @Retention(RetentionPolicy.RUNTIME)
   @Documented
  public @interface Logger {
  	String value() default "";
  }

3.切面功能实现

  package vip.appdesign.suncms.common.aspect;

  import cn.dev33.satoken.stp.StpUtil;
  import com.fasterxml.jackson.core.JsonProcessingException;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.SerializationFeature;
  import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
  import jakarta.servlet.http.HttpServletRequest;
  import lombok.extern.slf4j.Slf4j;
  import org.aspectj.lang.JoinPoint;
  import org.aspectj.lang.ProceedingJoinPoint;
  import org.aspectj.lang.annotation.AfterReturning;
  import org.aspectj.lang.annotation.Around;
  import org.aspectj.lang.annotation.Aspect;
  import org.aspectj.lang.annotation.Pointcut;
  import org.aspectj.lang.reflect.MethodSignature;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Component;
  import vip.appdesign.suncms.common.annotation.Logger;
  import vip.appdesign.suncms.common.utils.HttpContextUtils;
  import vip.appdesign.suncms.common.utils.UserAgentUtils;
  import vip.appdesign.suncms.module.sys.entity.SysLog;
  import vip.appdesign.suncms.module.sys.entity.SysUser;
  import vip.appdesign.suncms.module.sys.service.ISysLogService;

  import java.lang.reflect.Method;
  import java.time.LocalDateTime;


  /**
   * 系统日志,切面处理
   * @author sun
   * @date 2025/7/16
   */
  @Aspect
  @Component
  @Slf4j
  public class LoggerAspect {
  	@Autowired
  	private ISysLogService sysLogService;
  	//开始时间
  	ThreadLocal<Long> startTime = new ThreadLocal<>();

  	@Pointcut("@annotation(vip.appdesign.suncms.common.annotation.Logger)")
  	public void pointcut() {

  	}
  	@Around("pointcut()")
  	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  		//开始执行时间
  		 startTime.set(System.currentTimeMillis());
  		//执行方法,此处需要返回,否则控制器响应的时候
  		 return joinPoint.proceed();

  	}
  	@AfterReturning(value = "pointcut()",returning = "result")
  	public void afterReturning(JoinPoint joinPoint, Object result) {
  		//保存系统日志
  		saveSysLog(joinPoint,result);
  	}

  	private void saveSysLog(JoinPoint joinPoint,Object result) {
  		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  		Method method = signature.getMethod();
  		SysLog sysLog = new SysLog();
  		sysLog.setMethod(method.getName());
  		Logger logAnnotation = method.getAnnotation(Logger.class);
  		if (logAnnotation != null) {
  			sysLog.setOperation(logAnnotation.value());
  		}
  		//设置请求方法名称
  		String className = joinPoint.getTarget().getClass().getName();
  		String methodName = signature.getName();
  		sysLog.setMethod(className + "." + methodName + "()");
  		//设置请求参数和响应参数
  		Object[] args = joinPoint.getArgs();

  		ObjectMapper objectMapper = new ObjectMapper();
  		try {
  			//下面两行解决Java8新日期API序列化问题,在maven中添加jackson-datatype-jsr310依赖,否则参数有时间格式的就会会报错
  			objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  			objectMapper.registerModule(new JavaTimeModule());

  			String params = objectMapper.writeValueAsString(args);
  			String response = objectMapper.writeValueAsString(result);
  			sysLog.setParams(params);
  			sysLog.setResult(response);
  		} catch (JsonProcessingException e) {
  			log.error("参数转JSON字符串异常:{}",e.getMessage(),e);
  		}
  		//获取request
  		HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
  		//设置IP地址
  		String ipAddr = UserAgentUtils.getClientIp(request);
  		sysLog.setIp(ipAddr);
  		//设置消耗时间(毫秒)
  		sysLog.setTime(System.currentTimeMillis() - startTime.get());
  		sysLog.setCreateTime(LocalDateTime.now());
  		//设置执行用户信息
  		SysUser sysUser = (SysUser)StpUtil.getSession().get("user");//此处使用的Sa-Token登录认证,请结合自身环境进行修改
  		sysLog.setCreateBy(sysUser.getId());
  		sysLog.setUsername(sysUser.getUsername());
  		sysLogService.save(sysLog);

  	}

  }

4.对应SysLog实体示例,项目中引入了knife4j-openapi3规范,根据自身环境进行调整

package vip.appdesign.suncms.module.sys.entity;

import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import vip.appdesign.suncms.common.base.BaseEntity;

import java.io.Serial;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.EqualsAndHashCode;

/**
* 操作日志记录表 实体类。
*
* @author sun
* @since 2025-07-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(description = "操作日志记录表")
@Table("sys_log")
public class SysLog extends BaseEntity implements Serializable {

  @Serial
  private static final long serialVersionUID = 1L;

  /**
   * 日志主键ID
   */
  @Id(keyType = KeyType.Auto)
  @Schema(description = "日志主键ID")
  private Long id;

  /**
   * 用户名
   */
  @Schema(description = "用户名")
  private String username;

  /**
   * 用户操作
   */
  @Schema(description = "用户操作")
  private String operation;

  /**
   * 请求方法
   */
  @Schema(description = "请求方法")
  private String method;

  /**
   * 请求参数
   */
  @Schema(description = "请求参数")
  private String params;

  /**
   * 响应结果
   */
  @Schema(description = "响应结果")
  private String result;

  /**
   * 执行时常(毫秒)
   */
  @Schema(description = "执行时常(毫秒)")
  private Long time;

  /**
   * IP地址
   */
  @Schema(description = "IP地址")
  private String ip;

}

5.数据库SQL建表语句

  -- 操作日志记录
drop table if exists sys_log;
create table sys_log(
    id 						bigint(20) 		not null auto_increment                comment '日志主键ID',
    username                varchar(50)     default null                           comment '用户名',
    operation 				varchar(50) 	default null						   comment '用户操作',
    method 					varchar(100) 	default null 						   comment '请求方法',
    params                  varchar(5000)   default null                           comment '请求参数',
    result                  varchar(5000)   default null                           comment '响应结果',
    time                    bigint(20)      not null                               comment '执行时常(毫秒)',
    ip                      varchar(64)     default null                           comment 'IP地址',
    create_time 	        datetime 		default current_timestamp              comment '创建时间',
    create_by 		        bigint(20)      default null                           comment '创建人',
    is_deleted 	            tinyint(4)      default 0 							   comment '是否删除:0-否,1-是',
    primary key(id)
)engine=innodb auto_increment = 100 character set utf8mb4 collate = utf8mb4_unicode_ci comment '操作日志记录表';

6.控制类添加@Logger注解即可

    @PostMapping("save")
    @Logger("保存用户")
    @Operation(summary = "保存用户",description="保存用户表")
    public Result<Boolean> save(@RequestBody @Parameter(description="用户实体") SysUser sysUser) {
        ValidatorUtils.validate(sysUser);
        sysUser.setCreateBy(getUserId());
        //校验用户是否唯一
        boolean flag = iSysUserService.checkUserName(sysUser.getUsername(),sysUser.getId());
        if(flag) {
            return Result.of(iSysUserService.saveUser(sysUser),"保存成功","保存失败");
        }else {
            return Result.error("用户已经存在");
        }

    }

7.执行效果

image

posted @ 2025-07-16 23:34  appdesign  阅读(74)  评论(0)    收藏  举报