定义日志表结构
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='操作日志记录';
切面类环境搭建
依赖导入
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</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 // 手机端用户
}
日志切面类
@Aspect
@Component
@Slf4j
public class LogAspect { // 环绕通知切面类定义@Autowiredprivate AsyncOperLogService asyncOperLogService ;@Around(value = "@annotation(sysLog)")public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {// 构建前置参数SysOperLog sysOperLog = new SysOperLog() ;LogUtil.beforeHandleLog(sysLog , joinPoint , sysOperLog) ;Object proceed = null;try {proceed = joinPoint.proceed();// 执行业务方法LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 0 , null) ;// 构建响应结果参数} catch (Throwable e) { // 代码执行进入到catch中,// 业务方法执行产生异常e.printStackTrace(); // 打印异常信息LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 1 , e.getMessage()) ;throw new RuntimeException();}// 保存日志数据asyncOperLogService.saveSysOperLog(sysOperLog);// 返回执行结果return proceed ;}
}
自定义注解实现切面类使用
@Target({ElementType.TYPE}) //作用在类上
@Retention(RetentionPolicy.RUNTIME) //被修饰的注解的保留策略 在运行时依然可以通过反射得到注解中的信息
@Import(value = LogAspect.class) // 通过Import注解导入日志切面类到Spring容器中
public @interface EnableLogAspect {}
在启动类上添加@EnableLogAspect注解
在要添加日志功能的业务接口方法上添加Log注解,启动服务进行测试
@Log(title = "角色添加",businessType = 0) //添加Log注解,设置属性
@PostMapping(value = "/saveSysRole")
public Result saveSysRole(@RequestBody SysRole SysRole) {sysRoleService.saveSysRole(SysRole) ;return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
保存日志功能
定义一个与日志表相同的实体类
@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; // 请求URLprivate String operIp; // 主机地址private String operParam; // 请求参数private String jsonResult; // 返回参数private Integer status; // 操作状态(0正常 1异常)private String errorMsg; // 错误消息}
定义日志记录切面工具类
日志切面类调用工具
public class LogUtil {//操作执行之后调用public static void afterHandlLog(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());}
}
定义保存日志数据的service接口
//接口类
public interface AsyncOperLogService { // 保存日志数据public abstract void saveSysOperLog(SysOperLog sysOperLog) ;
}//实现类
@Service
public class AsyncOperLogServiceImpl implements AsyncOperLogService {@Autowiredprivate SysOperLogMapper sysOperLogMapper;@Async // 异步执行保存日志操作@Overridepublic void saveSysOperLog(SysOperLog sysOperLog) {sysOperLogMapper.insert(sysOperLog);}}
持久层接口
@Mapper
public interface SysOperLogMapper {public abstract void insert(SysOperLog sysOperLog);
}
映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.demo.spzx.mapper.SysOperLogMapper"><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></mapper>
在需要添加操作日志的接口方法上添加@Log注解进行测试。
@Log(title = "品牌列表",businessType = 0,operatorType = OperatorType.MANAGE)
//品牌列表(分页)
@GetMapping("/{page}/{limit}")
public Result list(@PathVariable Integer page,@PathVariable Integer limit) {PageInfo<Brand> pageInfo = brandService.list(page,limit);return Result.build(pageInfo,ResultCodeEnum.SUCCESS);
}
事务失效问题的解决
问题分析
Spring的事务控制是通过aop进行实现的,在框架底层会存在一个事务切面类,当业务方法产生异常以后,事务切面类感知到异常以后事务进行回滚。
当系统中存在多个切面类的时候,Spring框架会按照@Order注解的值对切面进行排序,@Order的值越小优先级越高,@Order的值越大优先级越低。优先级越高的切面类越优先执行,当我们没有给切面类指定排序值的时候,我们自定义的切面类的优先级和aop切面类的优先级相同,那么此时事务切面类的优先级要高于自定义切面类,那么切面类的执行顺序如下所示:
当在自定义切面类中对异常进行了捕获,没有将异常进行抛出,那么此时事务切面类是感知不到异常的存在,因此事务失效。
问题解决
解决方案一:使用@Order注解提高自定义切面类的优先级
解决方案二:在自定义切面类的catch中进行异常的抛出