日志注解,基于ruoyi的后置切面改进而来
有次接口响应时间太长,想知道具体接口执行的时间是多少,于是决定通过注解来实现这个想法,刚好ruoyi本身就提供了完善的日志注解,虽然是采用后置通知,但是完全不影响我们改造它。
想要实现接口耗时的功能,那就必须要获取到接口开始前和接口结束后的时间,后置通知肯定是不行了,但是环绕通知可以呀。
上代码,除了实现新的记录耗时功能外,原本的功能也保留了
package com.ruoyi.common.log.aspect; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessStatus;
import com.ruoyi.common.log.filter.PropertyPreExcludeFilter;
import com.ruoyi.common.log.service.AsyncLogService;
import com.ruoyi.common.security.service.utils.SecurityUtils;
import com.ruoyi.system.api.domain.SysOperLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map; /**
* 操作日志记录处理
*
* @author ruoyi
*/
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class); /**
* 排除敏感属性字段
*/
public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"}; @Autowired
private AsyncLogService asyncLogService; @Pointcut("@annotation(com.ruoyi.common.log.annotation.Log)")
public void pointCut() {
} /**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@Around("pointCut() && @annotation(log)")
public Object around(ProceedingJoinPoint joinPoint, Log log) {
return handleLog(joinPoint, log);
} protected Object handleLog(ProceedingJoinPoint joinPoint, Log log) {
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
String username = SecurityUtils.getUsername();
if (StringUtils.isNotBlank(username)) {
operLog.setOperName(username);
} // 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, log, operLog); try {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
//设置接口耗时
operLog.setCost(System.currentTimeMillis() - start);
////设置接口响应结果,如果响应结果为空或者是查询接口则赋予默认值
if (ObjectUtils.isEmpty(proceed) || HttpMethod.GET.toString().equals(operLog.getRequestMethod())) {
operLog.setJsonResult(Constants.OK_RESPONSE_JSON);
} else {
operLog.setJsonResult(JSONObject.toJSONString(proceed));
}
// 保存数据库
asyncLogService.saveSysLog(operLog);
return proceed;
} catch (Throwable e) {
Map<String, Object> errMap = new HashMap<>(1);
errMap.put("err_msg", e.getMessage());
errMap.put("stack_trace", e.getStackTrace()[0]);
operLog.setErrorMsg(JSONObject.toJSONString(errMap));
asyncLogService.saveSysLog(operLog);
throw new ServiceException(e.getMessage());
}
} /**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
*/
public void getControllerMethodDescription(ProceedingJoinPoint joinPoint, Log log, SysOperLog operLog) {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog);
}
} /**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
*/
private void setRequestValue(ProceedingJoinPoint joinPoint, SysOperLog operLog) {
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
} /**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
try {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
params.append(jsonObj).append(" ");
} catch (Exception ignored) {
}
}
}
}
return params.toString().trim();
} /**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter() {
return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
} /**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
代码也挺容易看懂的,毕竟日志功能不管在哪都很重要,下面是日志的实体类
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class SysOperLog extends BaseEntity
{
private static final long serialVersionUID = 1L; /** 日志主键 */
@Excel(name = "操作序号", cellType = ColumnType.NUMERIC)
private Long operId; /** 操作模块 */
@Excel(name = "操作模块")
private String title; /** 业务类型(0其它 1新增 2修改 3删除) */
@Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据")
private Integer businessType; /** 业务类型数组 */
private Integer[] businessTypes; /** 请求方法 */
@Excel(name = "请求方法")
private String method; /** 请求方式 */
@Excel(name = "请求方式")
private String requestMethod; /** 操作类别(0其它 1后台用户 2手机端用户) */
@Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户")
private Integer operatorType; /** 操作人员 */
@Excel(name = "操作人员")
private String operName; /** 部门名称 */
@Excel(name = "部门名称")
private String deptName; /** 请求url */
@Excel(name = "请求地址")
private String operUrl; /** 操作地址 */
@Excel(name = "操作地址")
private String operIp; /** 请求参数 */
@Excel(name = "请求参数")
private String operParam; /** 返回参数 */
@Excel(name = "返回参数")
private String jsonResult; /** 操作状态(0正常 1异常) */
@Excel(name = "状态", readConverterExp = "0=正常,1=异常")
private Integer status; /** 错误消息 */
@Excel(name = "错误消息")
private String errorMsg; @Excel(name = "接口耗时")
private long cost; /** 操作时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date operTime;
}
日志注解,基于ruoyi的后置切面改进而来的更多相关文章
- spring的AspectJ基于XML和注解(前置、后置、环绕、抛出异常、最终通知)
1.概念 (1)AspectJ是一个基于Java语言的AOP框架 (2)Spring2.0以后新增了对AspectJ切入点表达式的支持 (3)AspectJ是AspectJ1.5的新增功能,通过JDK ...
- Spring aop——前置增强和后置增强 使用注解Aspect和非侵入式配置
AspectJ是一个面向切面的框架,它扩展了java语言,定义了AOP语法,能够在编译期提供代码的织入,所以它有一个专门的编译器用来生成遵守字节码字节编码规范的Class文件 确保使用jdk为5.0以 ...
- spring 切面 前置后置通知 环绕通知demo
环绕通知: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:// ...
- Spring(二)--FactoryBean、bean的后置处理器、数据库连接池、引用外部文件、使用注解配置bean等
实验1:配置通过静态工厂方法创建的bean [通过静态方法提供实例对象,工厂类本身不需要实例化!] 1.创建静态工厂类 public class StaticFactory { private st ...
- Spring -- aop(面向切面编程),前置&后置&环绕&抛异常通知,引入通知,自动代理
1.概要 aop:面向方面编程.不改变源代码,还为类增加新的功能.(代理) 切面:实现的交叉功能. 通知:切面的实际实现(通知要做什么,怎么做). 连接点:应用程序执行过程期间,可以插入切面的地点. ...
- spring 基于xml的申明式AspectH中的后置通知的返回值获取
spring 基于xml的申明式AspectH中的后置通知的返回值获取 1. 配置文件 <aop:config> <aop:aspect ref="myAspect&quo ...
- 【Spring注解驱动开发】关于BeanPostProcessor后置处理器,你了解多少?
写在前面 有些小伙伴问我,学习Spring是不是不用学习到这么细节的程度啊?感觉这些细节的部分在实际工作中使用不到啊,我到底需不需要学习到这么细节的程度呢?我的答案是:有必要学习到这么细节的程度,而且 ...
- MyBatis 内置日志工厂基于运行时自省机制选择合适的日志工具
mybatis – MyBatis 3 | 日志 http://www.mybatis.org/mybatis-3/zh/logging.html MyBatis 内置日志工厂基于运行时自省机制选择合 ...
- Spring AOP前置通知和后置通知
Spring AOP AspectJ:Java社区里最完整最流行的AOP框架 在Spring2.0以上的版本中,可以使用基于AspectJ注解或基于XML配置的AOP 在Spring中启用Aspect ...
- Spring学习--基于 XML 的配置声明切面
正常情况下 , 基于注解的生命要优先于基于 XML 的声明. 通过 AspectJ 注解 , 切面可以与 AspectJ 兼容 , 而基于 XML 的配置则是 Spring 专有的.由于 Aspect ...
随机推荐
- ALV值存放图标
SPAN { font-family: "新宋体"; font-size: 12pt; color: rgba(0, 0, 0, 1); background: rgba(255, ...
- 如何修改vagrant系统的root用户密码
1. 先使用vagrant 用户登录. 2. $sudo passwd root #按照提示输入两次新的密码,并加以确认. 然后就可以修改root用户密码
- IPC,进程间通信
信号机制 也叫软中断,软件层次上对中断的模拟 kill -9 加进程号可以终止进程 linux下执行kill -l可以看到 这里面居然没有32 33 直接从31到34 所以一共是62个信号 1) SI ...
- WEB漏洞扫描工具之OWASP ZAP
添加扫描策略 使用新建的扫描策略
- 手写reduce()
function reduce(arr, callBack ,initVal){ if(!Array.isArray(arr) || !arr.length || typeof callBack != ...
- JS学习-从服务器获取数据
从服务器获取数据 Ajax 通过使用诸如 XMLHttpRequest 之类的API或者 - 最近以来的 Fetch API 来实现. 这些技术允许网页直接处理对服务器上可用的特定资源的 HTTP 请 ...
- 【linux】grep命令检索大批量日志中的堆栈日志
记得3年前,我为了查看100M日志文件里面的错误堆栈信息,百度了许久都毫无结果 没想到今天再次百度时,一下子看到了grep -A 命令,激动不已. 原来只需要用, grep -A 100 'KeyWo ...
- flutter RaisedButton 设置最小宽度和高度
flutter中可以通过ButtonTheme为RaisedButton设置最小宽度,示例代码如下: ButtonTheme( minWidth: 200.0,//设置最小宽度 height: 100 ...
- SQL-运算
dual表可以进行运算select * from dual; 算数运算 + - * /select 5 + 6 as aaa ,6 - 3 as bbb ,5 * 7 as ccc ,9/2 as d ...
- Java面向对象编程:多态(自我理解)
多态 (1)概念:同一个行为具有多个不同表现形式或形态的能力:就是同一个接口,使用不同的实例而执行不同的操作. (2)优点:消除类型之间的耦合关系:可替换性:可扩充性:接口性:灵活性:简化性: (3) ...