当前位置: 首页 > news >正文

后台管理系统的通用权限解决方案(十五)基于注解和切面实现操作日志记录

基于注解和切面实现操作日志记录

  • 1)创建一个新的模块opt-log,其结构如下

  • 2)创建注解@OptLog,当请求标注了该注解的Controller方法时,则表示需要记录操作日志
package com.itweid.opt.annotation;/*** 操作日志注解* @author: itweid* @since: 2024-11-13 13:05:05*/
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptLog {/*** 描述*/String value();/*** 记录执行参数*/boolean recordRequestParam() default true;/*** 记录返回参数*/boolean recordResponseParam() default true;}
  • 3)定义操作日志事件类OptLogEvent,用于封装操作日志AuthOptLog
package com.itweid.opt.event;import com.itweid.common.entity.AuthOptLog;
import org.springframework.context.ApplicationEvent;/*** 定义操作日志事件*/
public class OptLogEvent extends ApplicationEvent {public OptLogEvent(AuthOptLog authOptLog) {super(authOptLog);}
}
  • 4)创建切面类OptLogAspect,配置切入点拦截规则,拦截使用@OptLog注解标注的方法
package com.itweid.opt.aspect;import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.itweid.common.entity.AuthOptLog;
import com.itweid.common.exception.BaseException;
import com.itweid.common.pojo.BaseResult;
import com.itweid.jwt.pojo.JwtUserInfo;
import com.itweid.jwt.utils.AuthTokenUtils;
import com.itweid.opt.annotation.OptLog;
import com.itweid.opt.event.OptLogEvent;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;/*** @author: itweid* @since: 2024-10-25 09:23:57*/
@Slf4j
@Aspect
public class OptLogAspect {@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate AuthTokenUtils authTokenUtils;/*** 用于保存线程中的操作日志对象*/private static final ThreadLocal<AuthOptLog> THREAD_LOCAL = new ThreadLocal<>();/*** 定义Controller切入点拦截规则,拦截 @OptLog 注解的方法*/@Pointcut("@annotation(com.itweid.opt.annotation.OptLog)")public void optLogAspect() {}/*** 从ThreadLocal中获取操作日志对象,没有则创建一个*/private AuthOptLog getAuthOptLog() {AuthOptLog authOptLog = THREAD_LOCAL.get();if (authOptLog == null) {return new AuthOptLog();}return authOptLog;}/*** 前置通知,收集操作相关信息封装为Audit对象并保存到ThreadLocal中*/@Before(value = "optLogAspect()")public void doBefore(JoinPoint joinPoint) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();LocalDateTime now = LocalDateTime.now();AuthOptLog authOptLog = getAuthOptLog();// 开始时间authOptLog.setStartTime(now);// 创建时间authOptLog.setCreateTime(now);// 请求IPauthOptLog.setRequestIp(ServletUtil.getClientIP(request));// 浏览器authOptLog.setUa(StrUtil.sub(request.getHeader("user-agent"), 0, 500));// 类名authOptLog.setClassPath(joinPoint.getTarget().getClass().getName());// 方法名authOptLog.setActionMethod(joinPoint.getSignature().getName());// 请求地址authOptLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));// 请求类型authOptLog.setHttpMethod(request.getMethod());// 请求参数Object[] args = joinPoint.getArgs();String strArgs = "";try {if (!request.getContentType().contains("multipart/form-data")) {strArgs = JSONUtil.toJsonStr(args);}} catch (Exception e) {try {strArgs = Arrays.toString(args);} catch (Exception ex) {log.warn("解析参数异常", ex);}}authOptLog.setParams(StrUtil.sub(strArgs, 0, 65535));// 创建时间authOptLog.setCreateTime(now);// 获取 @OptLog 注解的信息MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();Method method = methodSignature.getMethod();OptLog ann = method.getAnnotation(OptLog.class);if (ann != null) {// 获取 @Api 注解的信息Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);if (api != null) {String[] tags = api.tags();if (tags != null && tags.length > 0) {// 操作描述authOptLog.setDescription(tags[0] + "-" + ann.value());}} else {// 操作描述authOptLog.setDescription(ann.value());}}// 操作员信息// 程序执行到这里时,已经在过滤器或拦截器中完成了权限的校验,所以这里不需要再进行校验try {String token = request.getHeader("X-token");if(token != null) {JwtUserInfo userFromToken = authTokenUtils.getUserFromToken(token);if(userFromToken != null) {authOptLog.setUserId(userFromToken.getUserId());authOptLog.setUserName(userFromToken.getAccount());}}} catch (BaseException e) {log.error("解析token时发生错误 {} {}", e.getCode(), e.getMessage());}log.info(authOptLog.showReqMsg());// 保存到线程容器THREAD_LOCAL.set(authOptLog);}/*** 成功返回通知*/@AfterReturning(returning = "ret", pointcut = "optLogAspect()")public void doAfterReturning(Object ret) {// 根据返回对象 ret 再做一些操作AuthOptLog authOptLog = getAuthOptLog();BaseResult baseResult = Convert.convert(BaseResult.class, ret);if (baseResult == null) {authOptLog.setType("OPT");} else {authOptLog.setResultCode(StrUtil.toString(baseResult.getCode()));authOptLog.setResultMsg(baseResult.getMessage());if (baseResult.isSuccess()) {authOptLog.setType("OPT");} else {authOptLog.setType("EX");}}authOptLog.setFinishTime(LocalDateTime.now());authOptLog.setConsumingTime(authOptLog.getStartTime().until(authOptLog.getFinishTime(), ChronoUnit.MILLIS));log.info(authOptLog.toResMsg());// 发布事件applicationContext.publishEvent(new OptLogEvent(authOptLog));THREAD_LOCAL.remove();}/*** 异常返回通知*/@AfterThrowing(throwing = "e", pointcut = "optLogAspect()")public void doAfterThrowable(Throwable e) {// 根据异常返回对象 e 再做一些操作AuthOptLog authOptLog = getAuthOptLog();authOptLog.setType("EX");authOptLog.setExDetail(getStackTrace(e));authOptLog.setFinishTime(LocalDateTime.now());authOptLog.setConsumingTime(authOptLog.getStartTime().until(authOptLog.getFinishTime(), ChronoUnit.MILLIS));log.info(authOptLog.toResMsg());// 发布事件applicationContext.publishEvent(new OptLogEvent(authOptLog));THREAD_LOCAL.remove();}public static String getStackTrace(Throwable throwable) {StringWriter sw = new StringWriter();try (PrintWriter pw = new PrintWriter(sw)) {throwable.printStackTrace(pw);return sw.toString();}}}

在这个切面类中,主要完成了以下几件事情:

  • 在切面类中定义切点,拦截Controller中添加@OptLog注解的方法

  • 在切面类中定义前置通知,在前置通知方法doBefore()中收集操作相关信息封装为AuthOptLog对象并保存到线程容器ThreadLocal

  • 在切面类中定义成功返回通知,在成功返回通知方法doAfterReturning中通过ThreadLocal获取AuthOptLog对象并继续设置其他的成功操作信息,随后发布事件

  • 在切面类中定义异常返回通知,在异常返回通知方法doAfterThrowable中通过ThreadLocal获取AuthOptLog对象并继续设置其他的异常操作信息,随后发布事件

  • 另外,在doBefore()方法中,会根据token信息解析得到本次操作的操作员信息,解析的方法参考上一节:后台管理系统的通用权限解决方案(十四)基于JWT实现登录功能

  • 5)创建操作日志事件监听类,监听OptLogEvent时间的发布,并将操作日志写入数据库
package com.itweid.auth.listener;import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.itweid.common.entity.AuthOptLog;
import com.itweid.opt.event.OptLogEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;/*** 操作日志监听器*/
@Slf4j
@Component
public class OptLogListener {// 异步监听OptLogEvent事件@Async@EventListener(OptLogEvent.class)public void saveAuthOptLog(OptLogEvent optLogEvent) {AuthOptLog authOptLog = (AuthOptLog) optLogEvent.getSource();// 将日志信息保存到数据库Db.save(authOptLog);log.info("监听到操作日志事件,已处理完毕...");}
}
  • 6)创建配置类OptLogConfiguration,用于注册切面类OptLogAspect
package com.itweid.opt.config;import com.itweid.opt.aspect.OptLogAspect;
import org.springframework.context.annotation.Bean;/*** @author: itweid* @since: 2024-11-08 09:27:44*/
public class OptLogConfiguration {@Beanpublic OptLogAspect optLogAspect() {return new OptLogAspect();}
}
  • 7)创建注解@EnableOptLog,导入配置类OptLogConfiguration,用于标识是否启用操作日志监听
package com.itweid.opt.annotation;import com.itweid.opt.config.OptLogConfiguration;
import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OptLogConfiguration.class)
@Documented
@Inherited
public @interface EnableOptLog {
}
  • 8)在启动类中标注@EnableOptLog@EnableAsync注解
@SpringBootApplication
@EnableAuthToken
@EnableAsync
@EnableOptLog
public class AuthServiceApp {public static void main(String[] args) {SpringApplication.run(AuthServiceApp.class, args);}
}
  • 9)创建一个测试用的Controller方法
@RestController
@RequestMapping("/auth-user")
@Api(value = "AuthUserController", tags = "用户操作")
public class AuthUserController {@ApiOperation("新增用户")@PostMapping("/save")@OptLog("新增用户")public BaseResult save(@RequestBody AuthUser authUser) {return BaseResult.setOk();}
}
  • 10)启动项目,访问http://127.0.0.1:8081/doc.html查看接口文档

  • 11)调试新增用户接口

日志输出如下:

数据库记录:

可见,前端发起请求时,后端都会生成一条操作日志信息记录下来,以方便后期的溯源等。

本节完,更多内容查阅:后台管理系统的通用权限解决方案


http://www.mrgr.cn/news/74894.html

相关文章:

  • 7.4、实验四:RIPv2 认证和触发式更新
  • 微搭低代码入门03函数
  • python共享全局变量的方案
  • 升级浏览器
  • 【ubuntu】Geogebra
  • C# 实现对指定句柄的窗口进行键盘输入的实现
  • 【Linux】 shell 学习汇总[转载]
  • Spark读MySQL数据rdd分区数受什么影响,读parquet、hdfs、hive、Doris、Kafka呢?
  • 接口自动化环境搭建
  • 连接数据库导出数据库信息支持excel pdf html markdown
  • 03 P1314 [NOIP2011 提高组] 聪明的质监员
  • 群控系统服务端开发模式-应用开发-前端角色功能开发
  • AI界盛会来袭!高录用EI会议(IS-AII 2025)你绝不能错过!
  • 【B+树特点】
  • Aippyy如何写论文?ai人工智能写作哪家好?
  • java项目-jenkins任务的创建和执行
  • DasViewer可以批量加载osgb格式文件吗?
  • C++初阶:类和对象(上)
  • Fiddler安装配置+抓包手机
  • Javascript 判断数据类型
  • IDEA中创建多模块项目步骤
  • 【2025国考|考公资料】轻松备考:你的公职考试全攻略,快速提升通过率!
  • 【实战场景】企业敏感词拦截如何实现?怎么避免员工发送违规词?这个方法大可一试!
  • 原型设计救星降临:深度解析5款超燃原型工具
  • 高端定制网站是什么样的?推荐几家优质网站建设公司!
  • [【comfyui教程】ComfyUI]Flux:非常好用的自定义多区域提示插件,精确绘图,多风格融合出图!