以后API的设计就按照这个标准来

最近改造了一个老项目,遇到了两个问题,一个是接口响应混乱,另一个是版本控制混乱,给开发和重构带来了巨大的不便,于是整理了下问题以及是如何解决的。

接口混乱

比如用户approve某个证书请求后,我们需要更新请求状态,然后将证书上传到第三方认证系统中去。 这个时候接口返回数据如下

json 复制代码
{
"success": true,
"code": 10001,
"info": "TE service not working",
"message": "OK",
"data": {
	"status": "Failed",
	"TeId": -1
	}
}

看success表示请求成功,看这个info又明显提示TE Service有问题, 然后这个data呢,看起来返回的数据也是错的。

后面我清理了下这个逻辑,发现实际上是状态变更成功了,但是将证书发送到TE失败了,因为TE服务这个时候不可用,所以code又返回了10001。

而且如果当状态变更时数据校验不过的时候返回的json数据又变成了这样子

json 复制代码
{
"success": false,
"code": 0,
"info": "",
"message": "current user is illegal",
"data": {
	"status": "Success",
	"TeId": 0
	}
}

对于这个情况为什么code返回的是0, message又提示有问题,data返回的数据看上去又是正常, 这里的info和message有啥区别呀?

最后看了代码才知道code, info, data是调用第三方服务的返回结果, success和message又是这个状态变更的处理结果。

可以看到这个返回的逻辑太过于混乱了, 不统一,如果不统一下response,后面做功能可是在太痛苦了。

于是经过讨论, 我把这个返回值定义成如下结构

java 复制代码
@Data
public class ApiResponse<T> {
	private boolean success;
	private T data;
	private int code;
	private String message;
}

然后解析流程如下

版本控制混乱

接口多版本定义不明确,我梳理了下我看到的集中定义方式

  1. /v1/api/user/list
  2. /api/user/list/v2
  3. /api/company/list?version=2
  4. 通过http header X-API-VERSION来定义

这几种方式URL path最直观也不容易出错,就是写法各种各样。 放到请求参数中又不容易携带,有侵入性,HTTP头方式没有侵入性,如果是部分接口需要那可以考虑下这种。

我的思路是在使用层面来进行统一,不让它侵入url中,开发的时候要更加易于使用,刚我之前的文章提到了RequestMappingHandlerMapping就是处理@RequestMapping注解的,所以我们可以自定义handlerMapping来处理

java 复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    String[] value();
}

public class ApiVersionRequestMapping extends RequestMappingHandlerMapping {

	// springmvc 5.3开始有这个类
    private final PathPatternParser pathPatternParser = new PathPatternParser();
    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        Class<?> controllerClass = method.getDeclaringClass();
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(controllerClass, ApiVersion.class);
        ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        // method上的version会覆盖class上面的
        if (methodAnnotation != null) {
            apiVersion = methodAnnotation;
        }
        String[] versions = apiVersion != null ? apiVersion.value() : new String[0];

        if (versions.length > 0) {
            for (String version : versions) {
                String versionedPath = "/" + version;
                // 把version添加到url中
                RequestMappingInfo newMapping = RequestMappingInfo
                        .paths(versionedPath)
                        .options(this.getBuilderConfiguration())
                        .build().combine(mapping);
                super.registerHandlerMethod(handler, method, newMapping);
            }
        } else {
            // 如果没有 ApiVersion 注解,使用原始的 mapping
            super.registerHandlerMethod(handler, method, mapping);
        }
    }

    @Override
    public RequestMappingInfo.BuilderConfiguration getBuilderConfiguration() {
        RequestMappingInfo.BuilderConfiguration config = super.getBuilderConfiguration();
        config.setPatternParser(pathPatternParser);
        return config;
    }
}

会在初始化的时候就扫描handler,入口可以看这里: org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet

需要注意的是需要把我们自定的mapping加入到SpringMVC中

java 复制代码
@Configuration
public class AppConfig implements WebMvcRegistrations {

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiVersionRequestMapping();
    }
}

现在我们就实现了下面的功能了。

java 复制代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @ApiVersion("v1")
    @RequestMapping("/user")
    public String getUserV1() {
        return "This is API version 1";
    }

    @ApiVersion("v2")
    @RequestMapping("/user")
    public String getUserV2() {
        return "This is API version 2";
    }

    @RequestMapping("/user")
    public String getUserDefault() {
        return "This is the default API version";
    }
}
  1. /v1/test/user 将访问 getUserV1() 方法
  2. /v2/test/user 将访问 getUserV2() 方法
  3. /test/user 将访问 getUserDefault() 方法
相关推荐
钟离墨笺8 分钟前
Go 语言-->指针
开发语言·后端·golang
拳打南山敬老院15 分钟前
从零构建一个插件系统(四)插件的缓存
javascript·架构
前端梭哈攻城狮28 分钟前
dify二开示例
前端·后端·python
该用户已不存在30 分钟前
Node.js 真的取代了PHP吗?
前端·后端·node.js
二闹36 分钟前
OpenCV识物:用代码“认出”物体
后端·opencv
花落人散处39 分钟前
SpringAI——接入高德MCP服务
java·后端
超浪的晨39 分钟前
Java 代理机制详解:从静态代理到动态代理,彻底掌握代理模式的原理与实战
java·开发语言·后端·学习·代理模式·个人开发
天天摸鱼的java工程师40 分钟前
🧠 MySQL 索引结构有哪些?优缺点是什么?【原理 + 场景实战】
java·后端·面试
java叶新东老师1 小时前
idea提交时忽略.class、.iml文件和文件夹或目录的方法
java·开发语言
阿宙ppppp1 小时前
基于yolov5+LPRNet+flask+vue的车牌识别(1)
后端·图像识别