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 掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送 本文版權歸作者所有,轉載請保留此宣告和原文連結 |