从壹开始前后端分离 [.netCore 填坑 ] 三十四║Swagger:API多版本控制,带来的思考
前言
大家周二好呀,.net core + Vue 这一系列基本就到这里差不多了,今天我又把整个系列的文章下边的全部评论看了一下(我是不是很负责哈哈),提到的问题基本都解决了,还有一些问题,已经在QQ群里讨论过了,今天再写一篇,然后给这个系列画一个暂时的句号吧,这些天也考虑写点儿啥,希望看到的小伙伴给点儿意见哟,其实我也是能力有限,不敢保证精通,不过只要想学,基本都能学到点儿东西的,至少至少能给大家在繁忙或者无聊的开发生涯中,多一点儿学习的动力吧,至少群里边的小伙伴是这样(马上破百了,快来加入我们吧),目前写的都是浅显的,打算下一步向架构师微服务方向简单拓展下,这两天简单看了一下,真是云里来雾里去,不是很通俗,还得自己啃,一起加油吧!!!
1、什么是版本控制
这个词语大家已经不会陌生,平时开发的时候,一定会用到过 Git 、SVN 或者 VSS (这三个我都用过,Git 应该是最好的),这个就是源代码的版本控制。
来句官方定义:版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。
那今天我们说的,就是 api接口的版本控制,这个大家一定也都接触到了,在我们使用的 swagger 中是这样的:
2、api版本控制的好处
简单来说,接口是APP的重要组成部分,数据是APP的核心,接口是连接APP和数据的纽带(这里的 APP 是广义上的接口调用者)。
一般情况下,我们项目中会有大量的接口,再加上版本的变化,接口的升级,一个接口,可能会有很多个稍有差异的接口,这个时候接口如果维护的不好,错一个就是一大片,那我们对 api 进行版本控制的好处有:
(1)有助于保护原有系统,不受影响,并及时修复问题
(2)可以实现用户的私人定制,(我之前接触过付费接口,可以这个意思)。
(3)快速迭代。
之前我在开发的时候,倒是没有考虑过这个问题,都是想当然的以为写代码只有一个版本,亦或者根本就没有版本概念,昨天晚上在看有一个小伙伴问到了 swagger 中,如何进行版本控制( 然后我想了想,在平时的开发中,我开发的项目中还没有遇到过版本控制,都是 web 项目+控制台项目,有问题就直接修改,有 bug 直接覆盖那种,从来没有考虑过版本,但是既然咱们这个系列是基于 api 接口的,版本应该是要有的,而且相信以后如果开发 api 项目的时候,也会遇到这个问题。我就研究了下 swagger 的源码,结合着网上的资料看了看,简单的配置了下,是这样的:
3、常见的版本控制有哪些?
通过上边的配置,我自认为很好的解决了这个问题,但是当我深入学习的时候,发现并不是,比如如何很好的调用不同版本的接口?,前端又如何对写好的接口地址进行快速修改?等等多个问题引起我的思考,通过搜索资料,我总结了以下,常见的版本控制有以下几个方案:
0、直接修改方法名,比如:/api/blog_v1,/api/blog_v2,/api/blog_v3... 虽然有时候也用,不过我直接 pass
1、通过路由控制,比如豆瓣:https://api.douban.com/v2/movie/in_theaters //本文重点说明,个人推荐,其他的大家可以参考博友文章
2、通过参数选择,比如:http://localhost:58427/api/Values?api-version=2.0
3、通过http请求的 Headers 来控制,接口地址不变,下边会说到
4、利用 content type 来控制
老张:本文只是一个说明版本,并没有把所有的方案都 code 出来,重点说了下路由控制,剩下的只是引导大家去思考这个问题,然后继续学习,毕竟会一两个方法就行了,平时开发中,使用的并不是很频繁,有好的想法欢迎下边留言,或者来群里和我们的小伙伴热情互动吧!
一、在 swagger 中通过路由实现版本控制
1、注册多个版本api
1、在 Blog.Core 项目下新建 SwaggerHelper 文件夹,然后添加 CustomApiVersion.cs 用来控制版本
2、在自定义API版本类中,添加枚举版本号
/// <summary> /// 自定义版本 /// </summary> public class CustomApiVersion { /// <summary> /// Api接口版本 自定义 /// </summary> public enum ApiVersions { /// <summary> /// v1 版本 /// </summary> v1 = 1, /// <summary> /// v2 版本 /// </summary> v2 = 2, } }
3、在项目启动类 Startup.cs 中,配置服务,遍历版本展示
在 ConfigureServices 方法内,修改 services.AddSwaggerGen 中的 c.SwaggerDoc 文档如下:
//遍历出全部的版本,做文档信息展示 typeof(ApiVersions).GetEnumNames().ToList().ForEach(version => { c.SwaggerDoc(version, new Info { // {ApiName} 定义成全局变量,方便修改 Version = version, Title = $"{ApiName} 接口文档", Description = $"{ApiName} HTTP API " + version, TermsOfService = "None", Contact = new Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" } }); });
4、修改 SwagerUI 调用配置
在 Configure 方法内,修改 app.UseSwaggerUI 如下:
app.UseSwaggerUI(c => { //之前是写死的 //c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1"); //c.RoutePrefix = "";//路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件 //根据版本名称倒序 遍历展示 typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); }); });
5、查看效果
现在已经实现了,在 swagger 中,进行多版本的展示,那要如何进行控制呢,请往下看。
2、对接口进行版本配置
1、刚刚我们已经创建好了多版本的接口文档,那现在就需要配置接口api了
在 BlogController.cs 中新建一个 V2_Blogtest() 方法:
/// <summary> /// 获取博客测试信息 v2版本 /// </summary> /// <returns></returns> [HttpGet] //MVC自带特性 对 api 进行组管理 [ApiExplorerSettings(GroupName = "v2")] //路径 如果以 / 开头,表示绝对路径,反之相对 controller 的想u地路径 [Route("/api/v2/blog/Blogtest")] public async Task<object> V2_Blogtest() { return Ok(new { status = 220, data = "我是第二版的博客信息" }); }
这里用到了 ApiExplorerSettings 特性,在mvc开发中,自带的一个组管理。
为什么要配置路径呢?是因为多版本的情况下,可能会出现重名函数,这里没有体现出来,因为使用的是 :V2_Blogtest ,下边的文章中会说到,如果一定要重名,需要怎么做。
2、这个时候查看效果,发现已经实现了我们文件开头的效果
这个时候效果已经实现了,但是这么写显然不是很方便,首先,我们的组名 GroupName 是写死的 ”v2“,不利用拓展,然后呢,还需要再一次配置路由 Route,有小伙伴就发现了,既然这两个都是特性,有没有办法重写一个特性,把这两个合并呢,欸?!就是这样,请往下看。
3、自定义路由特性,实现路由+版本 双控制
1、在根目录的 SwaggerHelper 文件夹下,新建一个 CustomRouteAttribute.cs
/// <summary> /// 自定义路由 /api/{version}/[controler]/[action] /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class CustomRouteAttribute : RouteAttribute, IApiDescriptionGroupNameProvider { /// <summary> /// 分组名称,是来实现接口 IApiDescriptionGroupNameProvider /// </summary> public string GroupName { get; set; } /// <summary> /// 自定义路由构造函数,继承基类路由 /// </summary> /// <param name="actionName"></param> public CustomRouteAttribute(string actionName = "[action]") : base("/api/{version}/[controller]/" + actionName) { } /// <summary> /// 自定义版本+路由构造函数,继承基类路由 /// </summary> /// <param name="actionName"></param> /// <param name="version"></param> public CustomRouteAttribute(ApiVersions version, string actionName = "[action]") : base($"/api/{version.ToString()}/[controller]/{actionName}") { GroupName = version.ToString(); } }
2、对 api 接口进行设置
/// <summary> /// 获取博客测试信息 v2版本 /// </summary> /// <returns></returns> [HttpGet] ////MVC自带特性 对 api 进行组管理 //[ApiExplorerSettings(GroupName = "v2")] ////路径 如果以 / 开头,表示绝对路径,反之相对 controller 的想u地路径 //[Route("/api/v2/blog/Blogtest")] //和上边的版本控制以及路由地址都是一样的 [CustomRoute(ApiVersions.v2, "Blogtest")] public async Task<object> V2_Blogtest() { return Ok(new { status = 220, data = "我是第二版的博客信息" }); }
浏览效果都是一样的,这里就不展示了,从这里看出来,还是很方便的。
说到这里,基于 swagger 的api接口版本控制已经说完了,采用的方法是路由控制,我个人感觉还是挺好的,当然文章的开头也说到了,还是有其他的方法,这里就简单的其中一个,个人不是很推荐,但是大家可以看看。
二、同名接口的版本控制
在上边咱们说到了,如果两个版本的方法名一定要一直咋办呢,重载大家肯定都知道,但是同一个 controller 接口方法肯定无论参数还是名称全部都一样,就连返回类型也一样,所以不能重载,那我们应该怎么办呢?,请往下看。
1、 在 controller 文件夹下,新建两个文件夹, v1、v2
2、然后添加相同的接口控制器 ApbController.cs,自定义即可
3、在两个控制器中,添加相同的代码
这样就能实现同名方法的版本控制了。
三、其他不适用于 swagger 的接口版本控制方法
这些方法我本打算写下来,发现不能通过 swagger 展示,会报错,只能通过 postman 测试,所以对我来说不是很完美,这里把博友的文章贴出来,大家可以自己看一下。
Your API versioning is wrong, which is why I decided to do it 3 different wrong ways