程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发

我的需求是 用户修改了基本信息,管理员从管理后台可以查询到相关的记录。

1 自定义 SysLog 注解
import java.lang.annotation.*;

/**
 * 系统日志注解
 * 
 * @author 早起的年轻人
 * @date 2023年3月8日
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

	String value() default "";
}
  • @Retention修饰注解,用来表示注解的生命周期,生命周期的长短取决于@Retention的属性RetentionPolicy指定的值
  • RetentionPolicy.SOURCE 表示注解只保留在源文件,当java文件编译成class文件,就会消失 源文件 只是做一些检查性的操作,
  • RetentionPolicy.CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 class文件(默认) 要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)
  • RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 运行时也存在 需要在运行时去动态获取注解信息
  • @Target 说明了Annotation所修饰的对象范围
  • 1.CONSTRUCTOR:用于描述构造器
  • 2.FIELD:用于描述域
  • 3.LOCAL_VARIABLE:用于描述局部变量
  • 4.METHOD:用于描述方法
  • 5.PACKAGE:用于描述包
  • 6.PARAMETER:用于描述参数
  • 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
2 自定义 @Aspect 切面获取用户行为

添加依赖如下,版本要对应自己的 SpringBoot 版本,我这使用的是 2.7.9 版本

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
     <version>2.7.9</version>
 </dependency>

AOP:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。而@Aspect 就是把一个类定义为切面供容器读取。

@Aspect
@Component
public class SysLogAspect {

  @Autowired
  private SysLogService sysLogService;

  /**
   * 自己定义的注解
   */
  @Pointcut("@annotation(com.biglead.demo.anno.SysLog)")
  public void logPointCut() {

  }
  @Around("logPointCut()")
  public Object around(ProceedingJoinPoint point) throws Throwable {
    long beginTime = System.currentTimeMillis();
    //执行方法
    Object result = point.proceed();
    //执行时长(毫秒)
    long time = System.currentTimeMillis() - beginTime;
    //保存日志
    saveSysLog(point, time);
    return result;
  }

  private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();

    SysLogEntity sysLog = new SysLogEntity();
    SysLog syslog = method.getAnnotation(SysLog.class);
    if (syslog != null) {
      //注解上的描述
      sysLog.setOperation(syslog.value());
    }

    //请求的方法名
    String className = joinPoint.getTarget().getClass().getName();
    String methodName = signature.getName();
    sysLog.setMethod(className + "." + methodName + "()");

    //请求的参数
    Object[] args = joinPoint.getArgs();
    try {
      String params = new Gson().toJson(args[0]);
      if (params.length() > 1000) {
        params = params.substring(0, 500);
      }
      sysLog.setParams(params);
    } catch (Exception e) {

    }

    //获取request
    HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
    //设置IP地址
    sysLog.setIp(IPUtils.getIpAddr(request));

    //用户名是用户ID
    String username = request.getHeader("userId");
    sysLog.setUsername(username);

    sysLog.setTime(time);
    sysLog.setCreateDate(new Date());
    //保存系统日志
    sysLogService.save(sysLog);
  }
}
  • @before: 前置通知,在方法执行之前执行。
  • @After:后置通知,在方法执行后执行。
  • @AfterReturning: 返回通知,在方法返回结果之后执行。
  • @AfterThrowing:异常通知,在方法抛出异常之后执行。
  • @Around:环绕通知,围绕着方法执行。
3 SysLogService 就是自己实现的操作数据保存这一系列
3.1 数据模型
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;


/**
 * 系统日志
 * 
 * @author 早起的年轻人
 * @date 2012-03-08 
 */
@TableName("sys_log")
@Data
@AllArgsConstructor
public class SysLogEntity implements Serializable {
	private static final long serialVersionUID = 1L;
	@TableId
	private Long id;
	//用户名
	private String username;
	//用户操作
	private String operation;
	//请求方法
	private String method;
	//请求参数
	private String params;
	//执行时长(毫秒)
	private Long time;
	//IP地址
	private String ip;
	//创建时间
	private Date createDate;
}
3.2 数据库sql
CREATE TABLE `sys_log` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   `username` varchar(50) DEFAULT NULL COMMENT '用户名',
   `operation` varchar(50) DEFAULT NULL COMMENT '用户操作',
   `method` varchar(200) DEFAULT NULL COMMENT '请求方法',
   `params` varchar(5000) DEFAULT NULL COMMENT '请求参数',
   `time` bigint(20) NOT NULL COMMENT '执行时长(毫秒)',
   `ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
   `create_date` datetime DEFAULT NULL COMMENT '创建时间',
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4043 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='系统日志';
3.3 Service
public interface SysLogService extends IService<SysLogEntity> {

    /**
     * 管理后台 分页查询日志
     * @return
     */
    Page queryPage(Integer pageInex, Integer pageSize);
}
@Service("sysLogService")
public class SysLogServiceImpl extends ServiceImpl<SysLogDao, SysLogEntity> implements SysLogService {

    @Override
    public Page queryPage(Integer pageInex, Integer pageSize) {

        Page<SysLogEntity> page = this.page(new Page<>(pageInex, pageSize), new QueryWrapper<>());

        return page;
    }
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.biglead.demo.pojo.SysLogEntity;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SysLogDao extends BaseMapper<SysLogEntity> {
	
}
4 Controller 中的使用

直接在 Controller 的方法上添加 @SysLog 注解就可以实现自动记录日志的行为

@Api(tags="用户模块")
@RestController
@RequestMapping("user")
public class UserController {
    @SysLog("修改用户信息")
    @PostMapping(value="/update")
    @ApiOperation(value = "修改用户信息")
    public Object updateUser(@RequestBody UserInfo userInfo) {
        return userService.updateUser(userInfo);
    }
}