📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
📢本文作者:由webmote 原创,首发于 【掘金】
📢作者格言: 生活在于折腾,当你不折腾生活时,生活就开始折腾你,让我们一起加油!💪💪💪
1.扩展场景
这是Swagger的的高阶应用篇,由于我以前项目中增加了框架级别的入参和出参定义,导致swagger无法识别外部的定义,仅仅识别为控制器方法的定义。
如果追根溯源,这个跟swagger也没太大的关系,应该是asp提供的ApiExplorer的问题,毕竟是静态反射,你想让他支持动态的过滤,这的确是勉为其难了。
2.swagger的结构
我使用的环境是asp.net core 3.1,swagger的包是Swashbuckle.AspNetCore 5.0.0。如果仅仅是产生一个api文档,为什么要关注这个Swagger的包,甚至结构呢,你说是吧!我当时就有股放下的冲动,给前端介绍下,让他们自己理解去... ...
作为一个接近产品的项目,我觉得有必要深入的理解下swagger的结构,完成这个转换就非常完美了。
说起swagger的结构,其实不如说是OpenAPI的结构,这个规范应该起源自swagger,微软定义了一套实现。
3. OPEN API 规范
记住微软的github地址:
OpenApi
这里简单过下规范的描述, 目前标准为
3.02
,
OpenApi的根文档对象如下:
# OpenAPI 规范版本号
openapi: 3.0.2
# API 元数据信息
info:
# 服务器连接信息
servers:
# API 的分组标签
tags:
# 对所提供的 API 有效的路径和操作
paths:
# 一个包含多种纲要的元素,可重复使用组件
components:
# 声明 API 使用的安全机制
security:
# 附加文档
externalDocs:
其中为了修改入参和出参,我们需要更改的是paths对象以及其引用的组件对象,组件对象封装可重用对象,然后通过 $ref 标签进行引用。
4. Path 对象的部分详解
swagger在启动后通过ApiExplorer获取到所有的控制器和方法信息,并进行组装,最后规范化OpenApi后输出到Json文件。
因此当某些对象不会操作时可以看下微软OpenApi的例程。这方面资料非常少,几乎没有借鉴的东东。
5. 我们的目标
我们的目标简言之是这样的,有一个Api,其定义如下: Resp A (Req req)
,经过swagger解析后正常描述为 接口A,入参:Req,结果Resp,而经过框架过滤后是需要解析为 接口A,入参: Args,结果Result,也可以理解为需要把目标接口解析为形如Result<Resp> A (Args<Req> req)
。
怎么干——之一
能否硬上?
我们利用swagger的过滤器,把ActionDescriptor定义的参数、方法都改掉。尝试中我放弃了,动态生成一个ActionDescriptor对象,难度太高了,其中大部分方法都是只读的,因此无法进行修改,只能重新构造一个全新的对象。
怎么干——之二
修改生成的json文件?显然没有类似的接口不太靠谱。最后经过不断浏览Swagger的源代码,终于发现了有四个过滤器:
IDocumentFilter
文档过滤器,对文档描述进行修改
IOperationFilter
Api级别过滤器,对不同的api进行定制化过滤
ISchemaFilter 模式过滤器,修改或增删组件定义的模型
IParameterFilter 参数过滤器,对特定参数进行过滤
经过筛选,有两类过滤器可以应用到本次活动中,IOperationFilter和ISchemaFilter,最后最合适的无非是 api级别的过滤器了!
6. 实现思路
上面已经确定了干活方向,那怎么实现呢?其实思路已经跃然纸上了,直接把入参和出参的Schema修改为泛型定义的入参和出参Schema即可。
代码如下:
public class MyOperationFilter : IOperationFilter
public void Apply(OpenApiOperation operation, OperationFilterContext context)
if (context.MethodInfo == null) return;
if(operation.RequestBody != null)
foreach(var c in operation.RequestBody.Content)
var oldRef = c.Value.Schema.Reference;
if(oldRef != null)
var pt = context.MethodInfo.GetParameters().FirstOrDefault().ParameterType;
c.Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiArgs<>).MakeGenericType(pt), context.SchemaRepository);
else if (c.Value.Schema?.Type?.ToLower() == "array")
var pt = context.MethodInfo.ReturnType;
c.Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiResult<>).MakeGenericType(pt), context.SchemaRepository);
if(operation.Responses != null)
foreach (var c in operation.Responses.Values)
var oldRef = c.Content.FirstOrDefault().Value?.Schema.Reference;
if (oldRef != null)
var pt = context.MethodInfo.ReturnType;
c.Content.First().Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiResult<>).MakeGenericType(pt), context.SchemaRepository);
else if(c.Content.FirstOrDefault().Value?.Schema.Type?.ToLower()=="array")
var pt = context.MethodInfo.ReturnType;
c.Content.First().Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiResult<>).MakeGenericType(pt), context.SchemaRepository);
7.最关键的点
c.Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiArgs<>).MakeGenericType(pt), context.SchemaRepository);
该语句在openapi的组件类中增加了个新的泛型对象,然后赋值给原来的schema,无需改动别的东东,生成的json随之更新,太完美了!!!
9. 小结
噢噢噢,愉快的周一结束了!
例行小结,理性看待!
结的是啥啊,结的是我想你点赞而不可得的寂寞。😳😳😳
👓都看到这了,还在乎点个赞吗?
👓都点赞了,还在乎一个收藏吗?
👓都收藏了,还在乎一个评论吗?
- 4237
-
webmote33
PostgreSQL
- 2479
-
webmote33
PostgreSQL