API升级,新旧版本的API共存,怎么管理呢?

一、前言

最近,单位APP做了升级,同步的,API也做了升级。

升级过程中,出现了一点问题:API升级后,旧API也需要保留,因为有旧的APP还在使用中。

那么,API端如何作到多个版本共存呢?

    为防止非授权转发,这儿给出本文的原文链接:https://www.cnblogs.com/tiger-wang/p/14167625.html

二、快速的解决办法

API的露出,是在API的Route定义中实现的。看下面的例子:

[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[Route("demo")]
public ActionResult<T> DemoFunc()
{
}
}

那我们知道,这个API的终结点是:/api/demo/demo。代码中[controller]是个可替换变量,编译时会替换为当前控制器的名称。

这个Route,里面的参数是个字符串,也就是说是可以随便换的。所以,对于多版本API,有个快速的办法,就是在里面做文章。

我们可以写成:

[Route("api/v1/[controller]")]
public class DemoController : ControllerBase
{
[Route("demo")]
public ActionResult<T> DemoFunc()
{
}
}

或者

[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[Route("v1/demo")]
public ActionResult<T> DemoFunc()
{
}
}

这样就区分出了版本号。

当然,这样做比较LOW,因为版本号是硬编码在代码中的。而且,这个改动会影响到API的终结点,例如上面两个变化,会让终结点变为:/api/v1/demo/demo/api/demo/v1/demo。如果前端可以方便修改,也算是一个方法。但对于我们APP已经上线运行来说,这个改动无法接受。

三、优雅的解决办法

这个方案,才是今天要说的核心内容。

首先,我们需要从Nuget上引入两个库:

% dotnet add package Microsoft.AspNetCore.Mvc.Versioning
% dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

这两个库,Versioning用来实现API的版本控制,Versioning.ApiExplorer用来实现元数据的发现工作。

引入完成后,修改Startup.cs

public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
}); services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
}); services.AddControllers();
}

就可以了。

这里面用了两个配置:AddApiVersioning,主要用来配置向前兼容,定义了如果没有带版本号的访问,会默认访问v1.0的接口。AddVersionedApiExplorer用来添加API的版本管理,并定义了版本号的格式化方式,以及兼容终结点上带版本号的方式。

到这儿,引入版本管理的工作就完成了。

使用时,就直接在控制器或方法上定义版本号:

[ApiVersion("1")]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[MapToApiVersion("2")]
[Route("demo")]
public ActionResult<T> DemoFunc()
{
}
}

这里面,又是两个属性:ApiVersion定义控制器提供哪个版本的API。这个属性可以定义多个。例如,我们控制器里既有v1的API,也有v2的API,我们可以写成:

[ApiVersion("1")]
[ApiVersion("2")]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
}

MapToApiVersion是API的版本定义,定义我们这个API是哪一个版本。

方法就这么简单。其它的,微软都帮我们做好了。

那,通常我们会用Swagger来做API文档。这个方法如何跟Swagger配合呢?

四、与Swagger的配合

Swagger也来自于Nuget的引用:

% dotnet add package swashbuckle.aspnetcore

引用后,通常我们Startup.cs里的配置是这样的:

public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo", Version = "V1" });
}); services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(option =>
{
option.SwaggerEndpoint("/swagger/v1/swagger.json", "Demo");
}); }

API多版本管理与Swagger配合,也有一个快速但比较LOW的方法:

public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo", Version = "V1" });
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo", Version = "V2" }); }); services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(option =>
{
option.SwaggerEndpoint("/swagger/v1/swagger.json", "Demo V1");
option.SwaggerEndpoint("/swagger/v2/swagger.json", "Demo V2");
});
}

这个方法也可以快速实现,不过跟上边的情况一样,版本号是硬编码的。

其实,也有另一个比较优雅的方式,就是手动实现IConfigureOptions<SwaggerGenOptions>和过滤IOperationFilter

先看Startup.cs里:

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(options => options.OperationFilter<SwaggerDefaultValues>()); services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(option =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
}

这里加了两个类,第一个ConfigureSwaggerOptions

internal class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider _provider;
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => _provider = provider; public void Configure(SwaggerGenOptions options)
{
foreach (var description in _provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
} private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info = new OpenApiInfo()
{
Title = "Demo API",
Version = description.ApiVersion.ToString(),
}; if (description.IsDeprecated)
{
info.Description += " 方法被弃用.";
} return info;
}
}

第二个SwaggerDefaultValues

internal class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated(); if (operation.Parameters == null)
return; foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
if (parameter.Description == null)
{
parameter.Description = description.ModelMetadata?.Description;
} if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
} parameter.Required |= description.IsRequired;
}
}
}

代码不一行行解释了,都是比较简单的。

运行,进入Swagger界面,右上角Select a definition,可以选择我们定义的版本号。

今天的配套代码已上传到Github,位置在:https://github.com/humornif/Demo-Code/tree/master/0035/demo

微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

最新文章

  1. WebForm 控件
  2. oracle 字符转字符串函数
  3. android关于The connection to adb is down, and a severe error has occured.这个问题的解决办法
  4. Ibatis.Net 各种配置说明(二)
  5. wap资费页面
  6. 【英语】Bingo口语笔记(10) - 常见词汇的缩读
  7. 前端基础系列——CSS规范(文章内容为转载)
  8. MVVM中轻松实现Command绑定(三)任意事件的Command
  9. HW3.19
  10. telnet简单操作 模拟请求
  11. 点击 下载 pdf
  12. js读取 存入cookie
  13. iOS 开源库介绍
  14. JAVA文件名命名规范
  15. python代码块和小数据池
  16. spring boot thymeleaf 标签未关闭报错
  17. python3安装ipython 过程以及问题
  18. django -- 修改admin 密码问题
  19. 初识 GitHub
  20. 【MySQL】【2】数字排序问题

热门文章

  1. vulnhub: DC 4
  2. 肝了一周的 UDP 基础知识终于出来了。
  3. appium每次运行不初始化
  4. Linux 学习笔记02丨Linux 概述、常用快捷键、apt命令
  5. 3.深入Istio:Pilot配置规则ConfigController
  6. mq checkpoint文件
  7. 【C++】C++之Lambda表达式
  8. Splay树求第k大模板
  9. Python学习随笔:使用xlwings读取和操作Execl文件
  10. 再也不怕 JavaScript 报错了,怎么看怎么处理都在这