使用AOP实现API访问日志

前言

在项目开发过程中,我们经过会遇到各种各样的问题,我们会尽可能地做很多记录,以便事后对错误进行复查。一些通常的手段就是使用Log文件保存每天的后台请求日志。为了方便排查和做到可视化操作,楼主今日通过学习Spring AOP后,引用AOP实现记录每次API访问日志。该日志主要记录每次访问API成功的请求,配合 错误日志拦截器SystemExceptionHandler,就可以对每一次请求做日志管理了。以下是实现方式:

记录实体
/**
 * 保存请求记录
 * @author Kellan_Song
 * @createTime 2019年4月4日
 */
@Entity
@Table(name="request_record")
public class RequestRecord implements Serializable {

	private static final long serialVersionUID = 1L;
	
	private Long id; //主键
	private String request; //请求参数
	private String response; //响应结果
	private Date createTime; //访问时间
	private String ip; //访问者ip
	private String apiAdr;  //请求地址
	
	public RequestRecord() {
		super();
	}
	
	public RequestRecord(String request, String response, Date createTime, String ip, String apiAdr) {
		super();
		this.request = request;
		this.response = response;
		this.createTime = createTime;
		this.ip = ip;
		this.apiAdr = apiAdr;
	}

	@Id
	@GeneratedValue
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	@Column(name="request",columnDefinition="text COMMENT '请求参数'")
	public String getRequest() {
		return request;
	}
	public void setRequest(String request) {
		this.request = request;
	}
	@Column(name="response",columnDefinition="text COMMENT '响应结果'")
	public String getResponse() {
		return response;
	}
	public void setResponse(String response) {
		this.response = response;
	}
	@Column(name="create_time",columnDefinition="DATETIME COMMENT '请求时间')
	public Date getCreateTime() {
		return createTime;
	}
	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}
	@Column(name="ip",columnDefinition="varchar(255) COMMENT '访问者ip'")
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	@Column(name="api_adr",columnDefinition="varchar(255) COMMENT '访问接口api'")
	public String getApiAdr() {
		return apiAdr;
	}
	public void setApiAdr(String apiAdr) {
		this.apiAdr = apiAdr;
	}
}
AOP拦截记录
/**
 * 请求记录拦截器
 * @author Kellan_Song
 * @createTime 2019年4月4日
 */
@Aspect
@Component
public class AopTest {
	private final Logger logger = LoggerFactory.getLogger(AopTest.class);
	@Autowired
	RequestRecordDaoI requestRecordDao;  //记录实体的dao层
	
	@Pointcut("execution(public * com.xhwl.seven.controller..*Controller.*(..))")
	public void pointCut(){}
	
	@Around("pointCut()")
	public Object arounds(ProceedingJoinPoint point) throws Throwable {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		String classType = point.getTarget().getClass().getName();
		Class<?> clazz = Class.forName(classType);
		String clazzName = clazz.getName();
		String methodName = point.getSignature().getName(); // 获取方法名称
		Method[] methods = clazz.getMethods();
		Annotation anno = null;
		String request_str = null;
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				//过滤只记录有@RequestMapping注解的方法
				anno = method.getAnnotation(RequestMapping.class);
				if (anno == null) return point.proceed();
				if (anno != null) {
				    //封装请求参数
					Map<String, Object> nameAndArgs = RequestUtils.getNameAndArgs(this.getClass(), clazzName, methodName, point.getArgs());
					request_str = StringUtils.toJSONString(nameAndArgs);
					logger.info("----请求参数 :" + request_str);
					break;
				}
			}
		}
		//继续执行
		Object o = point.proceed();
		// 获取放回结果
		String response_str = StringUtils.toJSONString(o);
		logger.info("----请求结果 :" + response_str);
		RequestRecord record = new RequestRecord(request_str, response_str, new Date(), request.getRemoteAddr(), request.getRequestURI());
		requestRecordDao.save(record);
		return o;
	}	
}
处理请求参数工具
/**
 * 封装请求参数; 因为AOP无法直接获取请求参数的key值,所以需要使用javassist动态代理的方式放射获取参数的key值。
 * @author Kellan_Song
 * @createTime 2019年4月3日
 */
public class RequestUtils {

	public static Map<String, Object> getNameAndArgs(Class<?> cls, String clazzName, String methodName, Object[] args)
			throws NotFoundException {

		Map<String, Object> nameAndArgs = new HashMap<String, Object>();
		//实例化类型池对象
		ClassPool pool = ClassPool.getDefault();
		ClassClassPath classPath = new ClassClassPath(cls);
		pool.insertClassPath(classPath);
		//获取方法参数
		CtClass cc = pool.get(clazzName);
		CtMethod cm = cc.getDeclaredMethod(methodName);
		MethodInfo methodInfo = cm.getMethodInfo();
		CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
		LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
		if (attr == null) {
			return nameAndArgs; //没有参数,返回空对象
		}
		//判断是否未静态方法
		int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
		for (int i = 0; i < cm.getParameterTypes().length; i++) {
			if (args[i] != null) {
				//过滤文件类型、请求类型
				if (args[i] instanceof MultipartFile || args[i] instanceof ServletRequest
						|| args[i] instanceof ServletResponse) {
					continue;
				}
				nameAndArgs.put(attr.variableName(i + pos), args[i]);// paramNames即参数名
			}
		}
		return nameAndArgs;
	}

}

版权声明:本文为q410654146原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。