日志注解,基于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 ...
随机推荐
- C++书写的第一个类
1.建立Student.h头文件 #ifndef STUDENT_H #define STUDENT_H #include <string> using namespace std; cl ...
- Source Tree 1、解决打开闪退问题2、解决找不到项目的问题
闪退问题主要做了两点 1.版本降到3.1.2 下载链接:Sourcetree Download Archives | Sourcetree 2.参考博客做出文件修改参考bigbig猿博客 新建json ...
- TinyMapper使用 对象映射框架
方便的进行对象转换,仅作使用笔记 https://www.cnblogs.com/arvinzhang/p/8282922.html 例子: var result = new List<Equi ...
- AtCoder Beginner Contest 272 - G - Yet Another mod M
随机 + 数论 题意 Submission #35524126 - AtCoder Beginner Contest 272 给一个长度为 \(n\;(1<=n<=5000)\) 的数组 ...
- CF1557总结
CF1557总结 Codeforces Round #737 (Div. 2) 先看了 A .意思是要把序列分成两个子序列,使得两序列各自平均值的和最小,输出最小值,要求 \(O(n)\) .想半天然 ...
- 查询某数据库的某字段存在于哪些表 mysql
select column_name,column_comment,data_type ,table_name from information_schema.columns where table ...
- Ribbit Mq 实现延迟消息
--------------------好记性不如烂笔头--------------------------- windows 环境,使用 rabbit Mq 需要安装, erl 和 rabbi ...
- Mysql学习:1、mysql安装及配置及连接Navicat
1.下载地址: https://dev.mysql.com/downloads/windows/installer/8.0.html 2.安装流程: a.选自定义安装:custom. b. 在下一步的 ...
- 微信小程序——计算器
知识点: wxml: hover-class: 实现点击态效果 hover-class样式显示的原理是 点击时把样式加到class的样式中,冲突时,谁在后面就显示谁! data-val: 用于传数据, ...
- webpack1.x 打包文件过大优化
1. 图片单独打包 module: { loaders: [ { test: /\.(jpe?g|png|gif|svg)$/, loader: 'url?limit=8192&name=./ ...