在进行系统开发的时候,系统提供给前端或者第三方使用的接口,要对接口的调用情况(接口的接收的参数、返回的结果、调用者、调用接口的ip等)进行记录。通过Spring AOP的***环绕通知***可以很容易实现该功能。实现该功能对调用接口数据的记录也便于后续项目出现问题的时候能够溯源
以下列出在项目中的实际用例。
项目中的架构如图:
SysInterfaceLogAspect :
package com.taoan.admin.common.aspect;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import com.taoan.admin.model.entity.SysAccount;
import com.taoan.admin.model.entity.SysInterfaceLog;
import com.taoan.admin.model.vo.AccountInfoVo;
import com.taoan.admin.repositories.SysInterfaceLogRespository;
import com.taoan.admin.util.RedisUtil;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
/**
* @project: taoan-admin-syssrc
* @description: 系统接口日志
* @author: zhangyingbin
* @create: 2020-06-06 14:20
**/
@Aspect
@Component
public class SysInterfaceLogAspect {
@Value("${KEY.TOKEN_KEY_PREFIX}")
private String TOKEN_KEY_PREFIX; //用户信息token前缀 根据自己项目来,可以不需要为下边根据redis获取当前登录者使用
@Resource
SysInterfaceLogRespository sysInterfaceLogRespository;
//TODO 设置切面根据自己的项目自己设置
@Pointcut("execution(* com.taoan.admin.controller.*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Long startTime = System.currentTimeMillis();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ipString = request.getHeader("x-forwarded-for");
if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
ipString = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
ipString = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(ipString) || "unknown".equalsIgnoreCase(ipString)) {
ipString = request.getRemoteAddr();
}
// 多个路由时,取第一个非unknown的ip
final String[] arr = ipString.split(",");
for (final String str : arr) {
if (!"unknown".equalsIgnoreCase(str)) {
ipString = str;
break;
}
}
//获取客户端类型
String agent = request.getHeader("user-agent");
String token = request.getHeader("token");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
long endTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); //获取到返回的数据,环绕通知在最后一定要返回去出去
//TODO 获取传递给接口的参数数据
Object parameter = getParameter(method, joinPoint.getArgs());
String urlStr = request.getRequestURL().toString();
SysInterfaceLog sysInterfaceLog = SysInterfaceLog.builder()
.startTime(startTime)
.clientType(agent)
.sessionId(ipString)
.endTime(endTime)
.method(request.getMethod())
.uri(request.getRequestURI())
.url(urlStr)
.basePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()))
.parameter(null == parameter ? null : JSONUtil.parse(parameter).toString())
.result(null == result ? null : JSONUtil.parse(result).toString())
.userName(getUser(token))
.build();
//TODO ApiOperation 获取swagger在controller中@ApiOperation(value="")对接口描述的value值,作为该被调用接口的描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
sysInterfaceLog.setDescription(apiOperation.value());
}
//将sysInterfaceLog存入数据库sys_interface_log表中 自己根据项目创建存入数据库的mapper,我这里用的JPA可以改用mybatis
sysInterfaceLogRespository.save(sysInterfaceLog);
return result;
}
/**
* 根据方法和传入的参数获取请求参数
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
//将RequestBody注解修饰的参数作为请求参数
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
if (requestBody != null) {
argList.add(args[i]);
}
//将RequestParam注解修饰的参数作为请求参数
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
if (requestParam != null) {
Map<String, Object> map = new HashMap<>();
String key = parameters[i].getName();
if (!StringUtils.isEmpty(requestParam.value())) {
key = requestParam.value();
}
map.put(key, args[i]);
argList.add(map);
}
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
/**
* @Description: 获取用户信息 该功能根据自己项目需求获取当前调用接口的调用者,我这里是根据token去redis内获取当前的登录者
* @Param: [token]
* @return: java.lang.String
* @Author: zhangyingbin
* @Date: 2020/6/6
*/
private String getUser(String token) {
if (StringUtils.isBlank(token)) {
return null;
}
AccountInfoVo accountInfoVo = RedisUtil.get(TOKEN_KEY_PREFIX + token, AccountInfoVo.class);
if (null == accountInfoVo){
return null;
}
return accountInfoVo.getAccountName();
}
}
实体类 SysInterfaceLog:
package com.taoan.admin.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* @project: taoan-admin-syssrc
* @description: 接口日志
* @author: zhangyingbin
* @create: 2020-06-06 13:59
**/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "SYS_INTERFACE_LOG")
public class SysInterfaceLog {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", length = 11, unique = true)
private Long id;
/**
* 操作者
*/
private String userName;
/**
* 回話id
*/
private String sessionId;
/**
* 客戶端類行
*/
private String clientType;
/**
* 开始调用时间
*/
private Long startTime;
/**
* 结束时间
*/
private Long endTime;
/**
* 操作描述
*/
private String description;
/**
* 请求类型
*/
private String method;
/**
* URI
*/
private String uri;
/**
* URL
*/
private String url;
/**
* 根路径
*/
private String basePath;
/**
* 请求参数
*/
private String parameter;
/**
* 请求返回的结果
*/
private String result;
}
对应的数据库表的脚本:
CREATE TABLE `sys_interface_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) DEFAULT NULL COMMENT '操作者',
`start_time` bigint(20) DEFAULT NULL COMMENT '开始时间',
`end_time` bigint(20) DEFAULT NULL COMMENT '结束时间',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`method` varchar(255) DEFAULT NULL COMMENT '请求类型',
`parameter` longtext,
`result` longtext COMMENT '结果',
`session_id` varchar(255) DEFAULT NULL COMMENT '回话id',
`client_type` varchar(255) DEFAULT NULL COMMENT '客户端类型',
`uri` varchar(255) DEFAULT NULL COMMENT 'uri',
`base_path` varchar(255) DEFAULT NULL COMMENT '根路径',
`url` varchar(255) DEFAULT NULL COMMENT 'url',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27217 DEFAULT CHARSET=utf8 COMMENT='系统接口日志';
版权声明:本文为bin929原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。