【Spring Boot 快速入门】二十、Spring Bo

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

前言

  在很多后台管理系统中,有明确的权限和角色的管控,当然也少不了操作日志的记录。本文将基于Spring 的AOP特性开发一个日志记录功能。下面记录一下整个开发工程

快速开始

  使用Spring的AOP特性,首先了解AOP是什么,AOP在程序开发过程中是指面向切面编程,通过预编译和动态代理实现程序功能。AOP中主要有切点、切面、连接点、目标群、通知、织入方式等。通知类型常用的有前置通知、环绕通知、后置通知等,在日志记录的过程中一般使用环绕通知。具体的AOP的相关概念大家不熟悉的可以去查询一下。

版本信息

  本次Spring Boot 基于AOP注解实现日志记录功能,主要版本信息如下:

1
2
3
sql复制代码Spring Boot 2.3.0.RELEASE
aspectjweaver 1.9.6
maven 3

  主要引入的依赖是aspectjweaver,如果aspectjweaver 和Spring Boot 版本不一致,可能会报找不到切点等相关的异常,可以替换位相关版本即可解决。

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>

基础信息

  实现日志记录功能,主要是将操作日志记录到数据库中或者搜索引擎中,方便查询。在日志中需要记录操作人、操作时间、请求的参数、请求的ip、请求的连接、操作类型等信息。本次示例的建表SQL如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
less复制代码CREATE TABLE `log_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`model` varchar(255) DEFAULT NULL COMMENT '模块',
`log_type` tinyint(4) DEFAULT NULL COMMENT '类型0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=登录,9=清空数据,10查询',
`url` varchar(255) DEFAULT NULL COMMENT '请求链接',
`method` varchar(255) DEFAULT NULL COMMENT '请求方法',
`class_name` varchar(255) DEFAULT NULL COMMENT '类名',
`method_name` varchar(255) DEFAULT NULL COMMENT '方法名',
`params` varchar(500) DEFAULT NULL COMMENT '请求参数',
`ip` varchar(255) DEFAULT NULL COMMENT 'ip地址',
`user_id` int(11) DEFAULT NULL COMMENT '操作人id',
`user_name` varchar(255) DEFAULT NULL COMMENT '操作人',
`sys_info` varchar(255) DEFAULT NULL COMMENT '系统信息',
`create_user` varchar(200) CHARACTER SET utf8 DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_user` varchar(200) CHARACTER SET utf8 DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`data_state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0 删除 1未删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

  本次将操作类型分为11类包登录、退出、增删改查、导入导出、清空等相关日志操作类别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
scss复制代码@Getter
@AllArgsConstructor
public enum LogTypeEnum {
/**
* 0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=登录,9=清空数据,10查询
* */
OTHER(0,"其它"),
ADD(1,"新增"),
UPDATE(2,"修改"),
DEL(3,"删除"),
AUTH(4,"授权"),
EXPORT(5,"导出"),
IMPORT(6,"导入"),
QUIT(7,"强退"),
GENERATE_CODE(8,"登录"),
CLEAR(9,"清空"),
QUERY(10,"查询"),
;


@EnumValue
private int value;

private String desc;
}

Log注解

  当数据初始化完成之后,就是编写一个自定义的Log注解。本次使用的注解主要是针对方法进行注解。具体如下:

  • @Target:注解的目标位置,主要可以有接口、类、枚举、字段、方法、构造函数、包等位置。可以根据需要进行配置。日志使用的本次基于方法注解。
  • @Retention:是指注解保留的位置,可以在源码中、类中、运行中。本次日志操作记录肯定是在运行中使用,所以选择RUNTIME。
  • @Documented:字面意思文档,也就是说明该注解将被包含在javadoc中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
less复制代码@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 模块
* */
String model() default "";

/**
* 操作
* */
LogTypeEnum logType() default LogTypeEnum.OTHER;
}

LogAspect

  定义一个日志的LogAspect切面。在类中需要使用@Aspect和
@Component指明这是一个切面方法在运行中进行扫描包。需要注意的是这个方法中需要定义切面和通知类型。最后根据注解和请求参数中的信息,查询到操作日志需要的信息。由于本次无需登录直接接口请求,所以操作人和操作id在演示中使用了默认值。本次示例只提取了部分操作日志信息,在项目中需要加入的日志信息多,可以根据需求进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
scss复制代码@Aspect
@Component
public class LogAspect {

@Resource
private LogInfoMapper logInfoMapper;

/**
* @ClassName logPointCut
* @Description:切点信息
* @Author JavaZhan @公众号:Java全栈架构师
* @Version V1.0
**/
@Pointcut("@annotation(com.example.demo.log.Log)")
public void logPointCut(){

}

/**
* @ClassName aroundForLog
* @Description:环绕通知
* @Author JavaZhan @公众号:Java全栈架构师
* @Version V1.0
**/
@Around("logPointCut()")
public Object aroundForLog(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = point.proceed();
saveSmsLog(point);
return result;
}


/**
* @ClassName saveLogInfo
* @Description: 保存操作日志
* @Author JavaZhan @公众号:Java全栈架构师
* @Version V1.0
**/
private void saveLogInfo(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogInfo logInfo = new LogInfo();
logInfo.setClassName(joinPoint.getTarget().getClass().getName());
logInfo.setMethodName(signature.getName());
Log log = method.getAnnotation(Log.class);
if(log != null){
logInfo.setModel(log.model());
logInfo.setLogType(log.logType().getValue());
}
Object[] args = joinPoint.getArgs();
try{
String params = JSONObject.toJSONString(args);
logInfo.setParams(params);
}catch (Exception e){

}
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
logInfo.setIp(IpUtils.getIpAddr(request));
logInfo.setUrl(request.getServletPath());
logInfo.setMethod(request.getMethod());
logInfo.setUserId(123);
logInfo.setUserName("admin");
logInfo.setCreateTime(new Date());
logInfo.setDataState(1);
//保存操作日志
logInfoMapper.insert(logInfo);
}
}

测试日志

  本示例将基于常用的接口进行测试。在Controller方法中,我们调用自定义注解,根据方法的实际使用含义指定方法的model信息和操作类型信息即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less复制代码@RequestMapping("user")
@Controller
public class UserController {

@Resource
private UserService userService;

@RequestMapping("getAllUser")
@ResponseBody
@Log(model = "查询用户列表",logType = LogTypeEnum.QUERY)
public List<User> getAllUser(){
return userService.getAllUser();
}


@RequestMapping("getUserById")
@ResponseBody
@Log(model = "获取指定用户",logType = LogTypeEnum.QUERY)
public User getUserById(Integer id ){
return userService.getById(id);
}
}

调用接口:http://127.0.0.1:8888/user/getAllUser 返回的数据信息如下。
图片.png
查看日志表中,可以看到已经新增一条查询用户列表的日志信息。
图片.png
下面访问其他接口:http://127.0.0.1:8888/user/getUserById?id=1 返回的数据信息如下。

图片.png
查看日志表中,可以看到已经新增一条获取指定用户的日志信息。
图片.png

结语

  好了,以上就是Spring Boot 基于AOP注解实现日志记录功能的示例,感谢您的阅读,希望您喜欢,如对您有帮助,欢迎点赞收藏。如有不足之处,欢迎评论指正。下次见。

  作者介绍:【小阿杰】一个爱鼓捣的程序猿,JAVA开发者和爱好者。公众号【Java全栈架构师】维护者,欢迎关注阅读交流。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%