Java利用AOP为业务执行记录日志
记录日志
记录日志:记录业务人员的操作日志【删除数据、修改数据、新增操作…】
记录日志的意义
后台管理系统记录操作日志的意义非常重要,主要体现在以下几个方面:
- 安全性:操作日志可以记录管理员操作行为,以此来监控和防止管理员滥用权限或进行其他不当操作。如果后台管理系统没有记录操作日志,那么一旦出现不当操作,就无法对其进行追踪和定位,造成不可估量的安全风险。
- 追溯性:操作日志可以帮助管理员及时发现问题,并可以通过日志进行快速定位和处理。例如某个用户投诉自己的订单异常,管理员可以直接通过查询该订单的操作日志,找到问题所在并进行修改或解决。
因此,后台管理系统记录操作日志,对于维护系统的安全稳定性、保障客户数据的完整性和隐私性、提高系统及时响应和处理能力等方面具有重要意义,是保障企业正常运营和客户满意度的重要手段。
在数据库中记录日志的表(建表语句)
CREATE TABLE `sys_oper_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`title` varchar(50) DEFAULT '' COMMENT '模块标题',
`business_type` varchar(20) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
`method` varchar(100) DEFAULT '' COMMENT '方法名称',
`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
`operator_type` varchar(20) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
`oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
`dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
`oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
`oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
`oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
`json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
`status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
`error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
`oper_time` datetime DEFAULT NULL COMMENT '操作时间',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb3 COMMENT='操作日志记录';
定义一个实体类与日志数据库表对应
@Data
public class SysOperLog extends BaseEntity {
private String title; // 模块标题
private String method; // 方法名称
private String requestMethod; // 请求方式
private String operatorType; // 操作类别(0其它 1后台用户 2手机端用户)
private Integer businessType ; // 业务类型(0其它 1新增 2修改 3删除)
private String operName; // 操作人员
private String operUrl; // 请求URL
private String operIp; // 主机地址
private String operParam; // 请求参数
private String jsonResult; // 返回参数
private Integer status; // 操作状态(0正常 1异常)
private String errorMsg; // 错误消息
}
AOP记录日志
AOP记录日志的主要优点包括:
- 低侵入性:AOP记录日志不需要修改原有的业务逻辑代码,只需要新增一个切面即可。
- 统一管理:通过AOP记录日志可以将各个模块中需要记录日志的部分进行统一管理,降低了代码重复度,提高了代码可维护性和可扩展性。
- 提升效率:通过引入AOP记录日志,可以避免手动编写日志记录代码,减少了开发人员的工作量,提升了开发效率。
- 安全性:通过AOP记录日志,可以收集系统的操作日志,帮助管理员及时发现问题并进行调整,从而提高系统的安全性。
AOP记录日志的整体思想
- 基于自定义注解来确定切入点【优势:可以通过自定义注解携带一些变化的参数,比如模块名称】
- 基于环绕通知来完成日志记录
切面类环境搭建
引入AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
实现方式,在需要记录日志的业务上加上我们自定义的注解来实现记录日志功能
自定义一个注解@Log
// 注解类型为方法注解
@Target({ElementType.METHOD})
// 在运行是执行
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
public String title() ; // 模块名称
public OperatorType operatorType() default OperatorType.MANAGE; // 操作人类别
public int businessType() ; // 业务类型(0其它 1新增 2修改 3删除)
public boolean isSaveRequestData() default true; // 是否保存请求的参数
public boolean isSaveResponseData() default true; // 是否保存响应的参数
}
上述操作人采用的是枚举的方式
public enum OperatorType { // 操作人类别
OTHER, // 其他
MANAGE, // 后台用户
MOBILE // 手机端用户
}
定义一个工具类,在这个工具类主要实现的就是环绕通知方法,在业务执行前和业务执行后的操作
public class LogUtil {
//操作执行之后调用
public static void afterHandleLog(Log sysLog, Object proceed,
SysOperLog sysOperLog, int status ,
String errorMsg) {
if(sysLog.isSaveResponseData()) {
sysOperLog.setJsonResult(JSON.toJSONString(proceed));
}
sysOperLog.setStatus(status);
sysOperLog.setErrorMsg(errorMsg);
}
//操作执行之前调用
public static void beforeHandleLog(Log sysLog,
ProceedingJoinPoint joinPoint,
SysOperLog sysOperLog) {
// 设置操作模块名称
sysOperLog.setTitle(sysLog.title());
sysOperLog.setOperatorType(sysLog.operatorType().name());
// 获取目标方法信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature() ;
Method method = methodSignature.getMethod();
sysOperLog.setMethod(method.getDeclaringClass().getName());
// 获取请求相关参数
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
sysOperLog.setRequestMethod(request.getMethod());
sysOperLog.setOperUrl(request.getRequestURI());
sysOperLog.setOperIp(request.getRemoteAddr());
// 设置请求参数
if(sysLog.isSaveRequestData()) {
String requestMethod = sysOperLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = Arrays.toString(joinPoint.getArgs());
sysOperLog.setOperParam(params);
}
}
sysOperLog.setOperName(AuthContextUtil.get().getUserName());
}
}
因为最后要操作数据库,所以需要有对应的业务层和持久层代码
业务层代码
接口
public interface AsyncOperaLogService {
public abstract void saveSysOperaLog(SysOperLog sysOperLog) ;
}
实现类
@Service
public class AsyncOperaLogServiceImpl implements AsyncOperaLogService {
@Autowired
private AsyncOperaLogMapper asyncOperaLogMapper;
// 异步保存日志信息
@Async
@Override
public void saveSysOperaLog(SysOperLog sysOperLog) {
// 把日志数据保存到数据库
asyncOperaLogMapper.insert(sysOperLog);
}
}
持久层代码
Mapper接口
@Mapper
public interface AsyncOperaLogMapper {
void insert(SysOperLog sysOperLog);
}
xml文件
<insert id="insert" >
insert into sys_oper_log (id,
title,
method,
request_method,
operator_type,
oper_name,
oper_url,
oper_ip,
oper_param,
json_result,
status,
error_msg)
values (#{id},
#{title},
#{method},
#{requestMethod},
#{operatorType},
#{operName},
#{operUrl},
#{operIp},
#{operParam},
#{jsonResult},
#{status},
#{errorMsg})
</insert>
定义一个切面类,并且在该切面类中提供一个环绕通知方法
@Aspect // 是一个切面类
@Component
public class LogAspect {
// 注入service
@Autowired
private AsyncOperaLogService asyncOperaLogService;
// 环绕通知
// sysLog 注解名称
@Around(value = "@annotation(sysLog)")
// ProceedingJoinPoint 能调用业务方法并且获取业务中的参数信息
public Object doAroundAdvice(ProceedingJoinPoint joinPoint, Log sysLog) {
// 创建操作日志的实体类
SysOperLog sysOperLog = new SysOperLog();
// 业务方法之前执行
LogUtil.beforeHandleLog(sysLog, joinPoint, sysOperLog);
// 调用业务方法
Object proceed = null;
try {
// 业务执行
proceed = joinPoint.proceed();
// 业务之后
LogUtil.afterHandleLog(sysLog, proceed, sysOperLog, 0, null);
} catch (Throwable e) {
e.printStackTrace();
LogUtil.afterHandleLog(sysLog, proceed, sysOperLog, 1, e.getMessage());
throw new RuntimeException();
}finally {
// 保存日志数据到数据库
asyncOperaLogService.saveSysOperaLog(sysOperLog);
}
// 返回执行结果
return proceed;
}
}
想让LogAspect这个切面类在其他的业务服务中进行使用,那么就需要该切面类纳入到Spring容器中。Spring Boot默认会扫描和启动类所在包相同包中的bean以及子包中的bean。而LogAspect切面类不满足扫描条件,因此无法直接在业务服务中进行使用。那么此时可以通过自定义注解进行实现,
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(value = LogAspect.class) // 通过Import注解导入日志切面类到Spring容器中
public @interface EnableLogAspect {
}
在启动类加上自己自定义的注解@EnableLogAspect
在启动类加上自己自定义的注解@EnableLogAspect
在启动类加上自己自定义的注解@EnableLogAspect
在需要记录日志的业务上加上注解即可
@Log(title = "角色菜单模块" , businessType = 2 )