赞
踩
随着项目的长期运行和迭代,积累的功能日益繁多,但并非所有功能都能得到用户的频繁使用或实际上根本无人问津。
为了提高系统性能和代码质量,我们往往需要对那些不常用的功能进行下线处理。
那么,该下线哪些功能呢?
此时,我们就需要对接口的调用情况进行统计和分析了!
以下内容为主要代码,完整代码请参考:https://gitee.com/regexpei/daily-learning-test
以下使用 自定义注解 + AOP 的方式,对接口调用进行记录。
1. 创建项目,添加依赖
<dependencies> <!-- 提供自动配置、日志、YAML等核心功能 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 提供面向切面编程支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- 用于构建Web,包括RESTful和基于Servlet的Web应用,包含了Spring MVC、Tomcat等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 通过注解减少样板代码的Java库,自动生成getter、setter等方法 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Swagger的注解库,允许开发者为API添加文档和元数据 --> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.6.2</version> </dependency> <!-- 用于Java对象的JSON序列化/反序列化的库,Fastjson的继任者 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.41</version> </dependency> <!-- 为Spring Boot应用提供了测试所需的依赖项,包括JUnit等,但仅限于测试阶段 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <!-- 排除已包含的SLF4J API版本,避免版本冲突 --> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <!-- Java工具包,提供了许多实用的工具类和方法 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency> </dependencies>
2. 自定义注解和实体类
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented @Inherited public @interface ApiOprLogAnno { @ApiModelProperty(value = "接口类型") String apiType() default ""; @ApiModelProperty(value = "接口说明") String apiDetail() default ""; @ApiModelProperty(value = "是否保存请求参数") boolean isSaveRequest() default false; @ApiModelProperty(value = "是否保存响应结果") boolean isSaveResponse() default false; }
@Setter @Getter public class ApiOprLog { @ApiModelProperty(name = "主键") private String id; @ApiModelProperty(name = "源IP") private String sourceIp; @ApiModelProperty(name = "用户名") private String username; @ApiModelProperty(name = "方法") private String method; @ApiModelProperty(name = "请求参数") private String reqParams; @ApiModelProperty(name = "响应结果") private String resResult; @ApiModelProperty(name = "异常信息") private String exMessage; @ApiModelProperty(name = "异常详细") private String exJson; @ApiModelProperty(name = "接口模块") private String apiModule; @ApiModelProperty(name = "接口类型") private String apiType; @ApiModelProperty(name = "接口说明") private String apiDetail; @ApiModelProperty(name = "创建时间") private Date createTime; @ApiModelProperty(name = "更新时间") private Date updateTime; }
3. 创建切面类
@Slf4j @Aspect @Component public class ApiOprAspect { @Value("${spring.application.name}") private String moduleName; /** * 从请求中获取 IP * * @return IP */ private static String getIpFromRequest() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) { HttpServletRequest request = (HttpServletRequest) requestAttributes .resolveReference(RequestAttributes.REFERENCE_REQUEST); return IpUtil.getRealIp(request); } return Constants.UNKNOWN; } @Pointcut("@annotation(cn.regexp.dailylearningtest.anno.ApiOprLogAnno)") public void pointcut() { } @Around("pointcut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { String id = IdUtil.fastSimpleUUID(); Object result; try { // 执行方法前操作 executeBefore(proceedingJoinPoint, id); result = proceedingJoinPoint.proceed(); // 执行方法后操作 executeAfter(proceedingJoinPoint, id, result); } catch (Throwable ex) { // 执行方法异常后操作 executeAfterEx(ex, id); throw ex; } return result; } private void executeBefore(ProceedingJoinPoint proceedingJoinPoint, String id) { // 获取目标方法的签名信息 MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); // 从方法签名中获取 ApiOprLogAnno 注解的信息 ApiOprLogAnno apiOprLogAnno = signature.getMethod().getAnnotation(ApiOprLogAnno.class); // 封装 ApiOprLog 对象 ApiOprLog apiOprLog = packaging(id, getIpFromRequest(), signature.toString(), apiOprLogAnno); if (apiOprLogAnno.isSaveRequest()) { // 保存请求参数 // 获取方法签名的参数名数组 String[] parameterNames = signature.getParameterNames(); // 获取连接点传递的实参数组 Object[] args = proceedingJoinPoint.getArgs(); Map<String, Object> paramMap = new HashMap<>(parameterNames.length); for (int i = 0; i < parameterNames.length; i++) { if (!RequestAttributes.REFERENCE_REQUEST.equals(parameterNames[i])) { paramMap.put(parameterNames[i], args[i]); } } apiOprLog.setReqParams(JSON.toJSONString(paramMap)); } // 入库操作 log.debug("executeBefore apiOprLog: {}", JSON.toJSONString(apiOprLog)); } private void executeAfter(ProceedingJoinPoint proceedingJoinPoint, String id, Object result) { // 获取目标方法的签名信息 MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); // 从方法签名中获取 ApiOprLogAnno 注解的信息 ApiOprLogAnno apiOprLogAnno = signature.getMethod().getAnnotation(ApiOprLogAnno.class); if (!apiOprLogAnno.isSaveResponse()) { return; } ApiOprLog apiOprLog = new ApiOprLog(); apiOprLog.setId(id); apiOprLog.setResResult(JSON.toJSONString(result)); apiOprLog.setUpdateTime(DateTime.now()); // 入库操作 log.debug("executeAfter apiOprLog: {}", JSON.toJSONString(apiOprLog)); } private void executeAfterEx(Throwable ex, String id) { ApiOprLog apiOprLog = new ApiOprLog(); apiOprLog.setId(id); apiOprLog.setExMessage(ex.toString()); apiOprLog.setExJson(ExceptionUtil.stacktraceToString(ex)); apiOprLog.setUpdateTime(DateTime.now()); // 入库操作 log.debug("executeAfterEx apiOprLog: {}", JSON.toJSONString(apiOprLog)); } /** * 封装 ApiOprLog * * @param id 主键 * @param sourceIp IP * @param method 方法 * @param apiOprLogAnno 注解 * @return 接口操作日志对象 */ private ApiOprLog packaging(String id, String sourceIp, String method, ApiOprLogAnno apiOprLogAnno) { ApiOprLog apiOprLog = new ApiOprLog(); apiOprLog.setId(id); apiOprLog.setSourceIp(sourceIp); apiOprLog.setUsername("Regexp"); apiOprLog.setMethod(method); apiOprLog.setApiModule(moduleName); apiOprLog.setApiType(apiOprLogAnno.apiType()); apiOprLog.setApiDetail(apiOprLogAnno.apiDetail()); apiOprLog.setCreateTime(DateTime.now()); return apiOprLog; } }
4. 进行测试
@Slf4j @RestController @RequestMapping("/user") public class UserController { @GetMapping("/get") @ApiOprLogAnno(apiType = "查询", apiDetail = "查询单个用户", isSaveResponse = true) public Person get() { return new Person("Regexp", 18); } @PostMapping("/save") @ApiOprLogAnno(apiType = "保存", apiDetail = "保存单个用户", isSaveRequest = true) public String save(@RequestBody Person person) { log.debug("save person: {}", JSON.toJSONString(person)); return "ok"; } @GetMapping("/getEx") @ApiOprLogAnno(apiType = "查询", apiDetail = "查询单个用户(异常情况)") public Person getEx() { throw new IllegalArgumentException(); } }
启动项目时,报错如下:
Caused by: java.lang.IllegalArgumentException: error Type referred to is not an annotation type: cn$regexp$dailylearningtest$anno$ApiOprLog
从报错信息来看,显示为:错误的类型,引用的不是一个注解类型。
Ctrl + Shift + F 全局搜索 ApiOprLog,看看哪些地方有用到 ApiOprLog。
经过搜索,发现在@annotation
中引用了 ApiOprLog(注解重命名后,这里忘记改了),但 ApiOprLog 并不是注解类型,所以导致启动项目时,Spring找到了这个类但这个类却不是注解,就报了这个错。
@Pointcut("@annotation(cn.regexp.dailylearningtest.anno.ApiOprLog)")
public void pointcut(){}
将 ApiOprLog 修改为正确的注解名称即可。
@Pointcut("@annotation(cn.regexp.dailylearningtest.anno.ApiOprLogAnno)")
public void pointcut(){}
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found
binding in
[jar:file:/D:/OpenSource/maven-repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in
[jar:file:/D:/OpenSource/maven-repository/org/slf4j/slf4j-reload4j/1.7.36/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an
explanation. SLF4J: Actual binding is of type
[ch.qos.logback.classic.util.ContextSelectorStaticBinder]
从以上信息来看,应该是发生了依赖冲突导致的。
在控制台输入 mvn dependency:tree
查看项目中所有使用的依赖以及依赖中引用的依赖,查找哪些依赖使用了 slf4j,在其中一个依赖中使用exclusions
进行排除即可,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。